Skip to main content

SF-0328 · Compare · Medium

What is the difference between Trigger.New and Trigger.Old?

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

Trigger.new is the list of records as the platform is about to save them (or has just saved them). Trigger.old is the list of those same records as they existed in the database before the change. The pair lets you detect deltas — “did this field change?” — and react accordingly.

Side-by-side

PropertyTrigger.newTrigger.old
TypeList<SObject>List<SObject>
ContentsPost-change records (or to-be-saved)Pre-change records (from the DB)
Available in before insertYes (writable)No
Available in before updateYes (writable)Yes (read-only)
Available in before deleteNoYes (read-only)
Available in after insertYes (read-only)No
Available in after updateYes (read-only)Yes (read-only)
Available in after deleteNoYes (read-only)
Available in after undeleteYes (read-only)No
MutableOnly in before contextsNever

The asymmetry is deliberate: on insert there is no “old” version, on delete there is no “new” version, and on undelete the previously deleted record is the new one being restored.

The pattern: detect a field change

trigger OpportunityTrigger on Opportunity (after update) {
    List<Opportunity> wonOpps = new List<Opportunity>();
    for (Opportunity opp : Trigger.new) {
        Opportunity prior = Trigger.oldMap.get(opp.Id);
        if (opp.StageName == 'Closed Won' && prior.StageName != 'Closed Won') {
            wonOpps.add(opp);
        }
    }
    if (!wonOpps.isEmpty()) {
        WinNotificationService.notify(wonOpps);
    }
}

This is the canonical “fire when a field transitions to value X” pattern. Compare same-Id records in Trigger.new and Trigger.oldMap and collect the rows that crossed the threshold.

What you cannot do

  • Assign to a field on a Trigger.old record. It’s read-only — and conceptually, the database state from before the transaction doesn’t exist anymore once the save lands.
  • Read Trigger.old in an insert trigger. There’s nothing to read. The reference is null.
  • Trust Trigger.old to reflect concurrent changes. It captures the row state at the start of the transaction; if another user updated it in parallel, you’ll see your read, not theirs. Use row locking (FOR UPDATE) if that matters.

Common interview follow-ups

  • Why is Trigger.old available on undelete? — It isn’t. Salesforce treats the restored row as a new entrant. Only Trigger.new is populated on after undelete.
  • What’s the read-only rule for Trigger.new in after context? — The save has already happened, so the runtime won’t accept in-place edits. To change a field after the fact, do update on a freshly constructed list (and guard against recursion).
  • Is Trigger.old cached? — It’s the snapshot the platform took before applying the change. It doesn’t refresh mid-transaction.

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