Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Salesforce Developer
hard

Walk me through writing a properly bulkified trigger handler.

The trigger framework pattern: trigger does nothing but delegate; logic lives in handler classes.

The trigger (one per object):

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

The handler base class:

public virtual class TriggerHandler {
    public void run() {
        if (Trigger.isBefore) {
            if (Trigger.isInsert) beforeInsert();
            if (Trigger.isUpdate) beforeUpdate();
        } else { // isAfter
            if (Trigger.isInsert) afterInsert();
            if (Trigger.isUpdate) afterUpdate();
            if (Trigger.isDelete) afterDelete();
        }
    }
    public virtual void beforeInsert() {}
    public virtual void beforeUpdate() {}
    public virtual void afterInsert() {}
    public virtual void afterUpdate() {}
    public virtual void afterDelete() {}
}

The Account-specific handler:

public class AccountTriggerHandler extends TriggerHandler {
    public override void afterUpdate() {
        // Bulkified pattern: collect Ids, query once, process in memory, DML once.
        Set<Id> changedAccIds = new Set<Id>();
        Map<Id, Account> oldMap = (Map<Id, Account>) Trigger.oldMap;
        for (Account a : (List<Account>) Trigger.new) {
            if (a.Phone != oldMap.get(a.Id).Phone) {
                changedAccIds.add(a.Id);
            }
        }
        
        if (changedAccIds.isEmpty()) return;
        
        List<Contact> toUpdate = new List<Contact>();
        Map<Id, Account> newMap = (Map<Id, Account>) Trigger.newMap;
        for (Contact c : [SELECT Id, AccountId, Phone FROM Contact WHERE AccountId IN :changedAccIds]) {
            c.Phone = newMap.get(c.AccountId).Phone;
            toUpdate.add(c);
        }
        
        if (!toUpdate.isEmpty()) update toUpdate;
    }
}

Why this is good:

  • One trigger per object — clear orchestration point.
  • Handler is testable — mock the trigger context in tests; no need to fire actual DML.
  • Bulkified — Sets, Maps, queries outside loops, single DML.
  • Conditional work — early return when nothing changed.
  • Type casts isolated — handler does the cast once.

Add as you grow:

  • A TriggerHandlerFactory that maps sObject name to handler class for dynamic dispatch.
  • A static recursion guard inside each handler to prevent re-entry.
  • Per-record action queues for cross-trigger collaboration (e.g., a handler updates state used by another handler in the same transaction).

Why this answer works

Senior. The complete pattern (trigger + base + concrete handler) is what differentiates a developer from someone who copy-pasted a tutorial. The recursion-guard mention is mature.

Follow-ups to expect

Related dictionary terms