after insert. The reason is structural: each Contact needs AccountId to link to its parent Account, and the parent Account’s Id only exists after the save commits. In a before insert trigger, Trigger.new is populated but the records have no Ids yet — there’s no key to put on the child Contact.
The reasoning, step by step
When Salesforce processes an insert, the order of relevant phases is:
- System validation (required fields, FLS, sharing).
- Custom validation rules.
before inserttrigger fires. Records exist only in memory. No Ids assigned.- The DML commits to the database. Ids are now assigned.
after inserttrigger fires.Trigger.newandTrigger.newMapare populated with Ids.- Roll-up summary recalculations, post-commit logic, post-commit events.
You can see why an attempt to do this in before insert fails:
trigger AccountAutoContacts on Account (before insert) {
List<Contact> toCreate = new List<Contact>();
for (Account a : Trigger.new) {
toCreate.add(new Contact(
LastName = 'Auto Contact',
AccountId = a.Id // <-- a.Id is null here
));
}
insert toCreate; // links every Contact to a null parent
}
The Contacts get inserted, but they’re orphaned — AccountId = null — because a.Id wasn’t yet assigned when we read it. That defeats the entire purpose of the rule.
The right shape
trigger AccountAutoContacts on Account (after insert) {
List<Contact> toCreate = new List<Contact>();
for (Account a : Trigger.new) {
for (Integer i = 1; i <= 3; i++) {
toCreate.add(new Contact(
FirstName = 'Auto',
LastName = a.Name + ' Contact ' + i,
AccountId = a.Id // populated — we're after the save
));
}
}
if (!toCreate.isEmpty()) insert toCreate;
}
A second reason: Trigger.new is read-only in after, and that’s OK here
In after triggers, the records in Trigger.new cannot be modified — they’re already saved. That restriction would matter if the rule said “set a field on the Account itself”; it doesn’t matter here because we’re inserting child Contacts, not editing the Account.
A useful framing for “before vs after”:
| Goal | Trigger phase |
|---|---|
| Default or sanitize fields on the record being saved | before (insert/update) |
| Run logic that depends on the record’s Id | after (insert/update) |
| Insert/update/delete related records | after |
Block the save with addError | before |
The 3-contacts-per-Account rule is “insert related records” → after insert.
What about a recursive risk?
When you insert Contacts inside the Account trigger, any Contact trigger on the org also fires. That’s normal — Salesforce trigger recursion is one level deep here (Account → Contact), not infinite. Watch out only if you have logic that updates the Account from within the Contact trigger, which could re-enter AccountTrigger.after update. Standard recursion guards handle that.
A subtler version of the question
Interviewers sometimes follow up: “What if the rule says default the Account’s Type__c to ‘Prospect’ AND create three contacts?” Now you split:
trigger AccountTrigger on Account (before insert, after insert) {
if (Trigger.isBefore && Trigger.isInsert) {
for (Account a : Trigger.new) {
if (a.Type == null) a.Type = 'Prospect';
}
}
if (Trigger.isAfter && Trigger.isInsert) {
// create 3 contacts per account
}
}
The default goes in before (mutate the record in flight, save once). The child creation goes in after (need the parent Id).
What interviewers are really looking for
The one-word answer is “after”. The signal is why: parent Id is needed and only exists post-save. Bonus points for naming the matrix — Trigger.newMap is null in before insert for the same reason — and for explaining the “before mutates the record being saved; after touches related records” rule of thumb.
Verified against: Apex Developer Guide — Triggers and Order of Execution, Trigger Context Variables. Last reviewed 2026-05-17.