Skip to main content

SF-0324 · Concept · Easy

Explain the different types of triggers?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026

Apex triggers come in two timing categories (before and after) running against five DML events (insert, update, delete, undelete, and the after merge variant). That gives you the seven canonical trigger contexts every Salesforce developer is expected to know:

ContextWhen it firesTypical use
before insertAfter validation, before the row hits the databaseSet defaults, derive fields, validate values
before updateBefore changes are persistedSame as above + reject changes via addError
before deleteBefore the row is removedBlock deletion if related records exist
after insertRow now has an IdCreate related child records, fire callouts (via async)
after updateUpdated row is persisted, old values still accessible via Trigger.oldRoll up to parents, audit, notify
after deleteRow is gone, accessible via Trigger.oldCleanup of dependent rows, audit trail
after undeleteRestored from Recycle BinRe-establish derived state

Before triggers — fast, in-place, no extra DML

trigger AccountBefore on Account (before insert, before update) {
    for (Account a : Trigger.new) {
        if (a.Industry == null) a.Industry = 'Unspecified';
        if (a.Rating == null && a.AnnualRevenue >= 10_000_000) a.Rating = 'Hot';
    }
}

Notice — no update statement at the bottom. In before context, Trigger.new is the records about to be written. Just mutate them; the platform persists what you leave behind. Doing an explicit update would cause a recursion error.

After triggers — read the world, react to it

trigger OpportunityAfter on Opportunity (after update) {
    Set<Id> dirtyAccountIds = new Set<Id>();
    for (Opportunity opp : Trigger.new) {
        Opportunity old = Trigger.oldMap.get(opp.Id);
        if (opp.StageName != old.StageName && opp.IsWon) {
            dirtyAccountIds.add(opp.AccountId);
        }
    }
    if (!dirtyAccountIds.isEmpty()) {
        AccountRollupService.recalcWonRevenue(dirtyAccountIds);
    }
}

The pattern here is the same one you’ll write a hundred times: walk Trigger.new, compare to Trigger.oldMap, collect IDs to act on, then delegate.

The two pitfalls every interviewer probes

1. Trying to modify Trigger.new in an after trigger. Records are read-only in after context. The reasoning: they’ve already been persisted, so the runtime doesn’t have a “draft” copy you can edit in place. You’d need to construct a new list, write to it, and update — which fires the trigger again and risks recursion.

2. Forgetting Trigger.oldMap is null in before insert. New records don’t have IDs yet, so there’s no map keyed by Id. If you reach for Trigger.oldMap in before insert, you’ll get a NullPointerException. The right place to compare old vs new is before update or after update.

The “one trigger per object” rule

Salesforce doesn’t enforce trigger order. If you have three triggers on Account, they fire in arbitrary order, and that order can change between deployments. The community consensus — and Salesforce’s own architectural guidance — is one trigger file per object, delegating to a handler class:

trigger AccountTrigger on Account (
    before insert, before update, before delete,
    after insert, after update, after delete, after undelete
) {
    new AccountTriggerHandler().run();
}

The handler decides which context-specific method to call based on Trigger.operationType. This gives you a single, ordered place to control execution and makes the trigger itself trivial.

Common interview follow-ups

  • Why one trigger per object? — Predictable order, single test target, easier to add recursion guards and skip flags.
  • Can you call a trigger from another trigger? — Indirectly, yes — any DML inside a trigger fires the same-object trigger again. That’s why every framework has a recursion guard.
  • Which context for which problem? — Default to before when you’re mutating the record itself; default to after when you’re touching anything else.

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