Skip to main content

SF-0330 · Concept · Medium

Availability matrix for Trigger.New, Trigger.NewMap, Trigger.Old and Trigger.Old Map?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

Half the NullPointerExceptions in trigger code come from reading a context variable that doesn’t exist in the current event. The fix is to memorise the availability matrix and check the event before dereferencing.

The matrix

EventTrigger.newTrigger.newMapTrigger.oldTrigger.oldMap
before insertYes (no Ids)No (Ids not assigned yet)NoNo
after insertYes (with Ids)YesNoNo
before updateYesYesYesYes
after updateYesYesYesYes
before deleteNoNoYesYes
after deleteNoNoYesYes
after undeleteYesYesNoNo

A few clauses to internalise:

  1. before insert has no newMap — the platform hasn’t assigned record IDs yet, so a map keyed by Id has no key. Use for (X x : Trigger.new) instead.
  2. Deletes only expose old — there are no “new” records during a delete.
  3. Undelete only exposes new — the records are coming back, so there’s no “old” version.
  4. Trigger.new is editable only in before triggers — modifying Trigger.new in after insert or after update throws a runtime error.

How Trigger.old differs from Trigger.new

Both are typed lists, but they represent different snapshots:

  • Trigger.new — the version of the record as it will be saved (in before) or as it just was saved (in after).
  • Trigger.old — the version of the record before the user’s change.

The classic field-change check uses both:

for (Account a : Trigger.new) {
    Account before = Trigger.oldMap.get(a.Id);
    if (before.Industry != a.Industry) {
        // industry changed in this update
    }
}

This pattern only works in update events — oldMap isn’t populated in insert/delete/undelete.

Why Trigger.newMap is null in before insert

The map key is the record’s Salesforce Id. During before insert, the records haven’t been written to the database — they don’t have Ids yet. The platform skips populating newMap rather than handing you a map with null keys. After insert runs after the save, so by after insert, every record has an Id and newMap is fully populated.

Why Trigger.new is read-only in after

The save is already done. Mutating Trigger.new in an after trigger would mean updating already-saved fields, which the platform would have to write a second time — an implicit recursive DML. So it just disallows it:

trigger AccountAfter on Account (after update) {
    for (Account a : Trigger.new) {
        a.Industry = 'Tech'; // throws: System.FinalException: Record is read-only
    }
}

If you need to change a field after a save, requery, copy, and update explicitly — but better still, do the work in a before update trigger to avoid the second DML.

A safe-access helper

A common production pattern is a small handler-class helper that hides the matrix behind named methods:

public abstract class TriggerHandler {
    protected List<SObject> newRecs() {
        return Trigger.new == null ? new List<SObject>() : Trigger.new;
    }
    protected Map<Id, SObject> oldMap() {
        return Trigger.oldMap == null ? new Map<Id, SObject>() : Trigger.oldMap;
    }
    // ...
}

Now downstream code calls newRecs() and never branches on which event fired.

Common interview follow-ups

  • Why is oldMap null in inserts? — Because there’s no prior version of a brand-new record.
  • Can I look up a record’s old value during after delete? — Yes — Trigger.old and Trigger.oldMap are both populated; they’re the only way to see what was deleted.
  • Why does Trigger.new exist in after insert if I can’t change it? — So you can run logic that needs the Ids — typically creating related records, queuing async jobs, sending events.

What interviewers are really looking for

Reciting the matrix is the start. The senior signal is explaining why — newMap is empty in before insert because Ids don’t exist yet, Trigger.new is read-only in after because the save is committed. Mention that mutating Trigger.new in after throws FinalException, and that field-change detection requires the update event to compare oldMap against new.

Verified against: Apex Developer Guide — Trigger Context Variables. Last reviewed 2026-05-17.