Skip to main content

SF-9223 · Scenario · Medium

Write a trigger to update child records when a parent record is updated

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

The textbook version of this trigger is three lines. The version that survives production is a bulkified, change-detection-aware handler with a single SOQL and a single DML. Interviewers test this question precisely because the textbook version always breaks at scale.

The 60-second answer

Use an after-update trigger on the parent. Collect parent IDs where the relevant field actually changed (diff Trigger.new vs Trigger.oldMap). Query the child records in one SOQL filtered by the parent IDs. Mutate the child records in memory. Do one DML to update them. Guard against recursion via a handler-class static flag. Never query or DML inside a for loop.

The full pattern — Account name change cascades to Contacts

Scenario: when an Account’s Name changes, copy the new name onto a Account_Name_Snapshot__c field on every related Contact.

trigger AccountTrigger on Account (after update) {
    AccountTriggerHandler.cascadeNameToContacts(Trigger.new, Trigger.oldMap);
}
public class AccountTriggerHandler {

    public static Boolean alreadyRunCascade = false;

    public static void cascadeNameToContacts(
        List<Account> newAccts,
        Map<Id, Account> oldAccts
    ) {
        if (alreadyRunCascade) return;
        alreadyRunCascade = true;

        // 1. Collect parent IDs where the trigger field actually changed
        Map<Id, String> changedNameById = new Map<Id, String>();
        for (Account a : newAccts) {
            Account oldA = oldAccts.get(a.Id);
            if (a.Name != oldA.Name) {
                changedNameById.put(a.Id, a.Name);
            }
        }
        if (changedNameById.isEmpty()) return;

        // 2. ONE SOQL — pull every Contact under any changed Account
        List<Contact> children = [
            SELECT Id, AccountId, Account_Name_Snapshot__c
            FROM   Contact
            WHERE  AccountId IN :changedNameById.keySet()
        ];
        if (children.isEmpty()) return;

        // 3. Mutate in memory
        for (Contact c : children) {
            c.Account_Name_Snapshot__c = changedNameById.get(c.AccountId);
        }

        // 4. ONE DML
        update children;
    }
}

What every interviewer is grading on

ElementWhat the grader is looking for
after update (not before)The parent must be saved first; before-update can’t see the committed value
Change detection via Trigger.oldMapAvoid pointless DML when the field didn’t change
Bulkified SOQLOne query, not one per parent — survives 200-record context
Filter by AccountId IN :setDoesn’t pull every Contact in the org
Single bulk DMLDoesn’t burn 150 DML statements on 150 parents
Recursion guardThe Contact update could fire a Contact trigger that updates Account — guard breaks the cycle
Null-safetySome Contacts have AccountId = null — filtered out by the IN clause naturally, but worth saying

Miss any of these and the interviewer’s next question is “what happens at 10,000 records?”

Same pattern, parent-to-grandchild (Account → Opportunity → OpportunityLineItem)

trigger AccountTrigger on Account (after update) {
    Set<Id> changedAcctIds = new Set<Id>();
    for (Account a : Trigger.new) {
        Account oldA = Trigger.oldMap.get(a.Id);
        if (a.Currency_Code__c != oldA.Currency_Code__c) {
            changedAcctIds.add(a.Id);
        }
    }
    if (changedAcctIds.isEmpty()) return;

    // Walk the relationship in ONE SOQL via parent-to-child sub-select
    List<OpportunityLineItem> linesToUpdate = new List<OpportunityLineItem>();
    for (Account a : [
        SELECT Id, Currency_Code__c,
            (SELECT Id,
                (SELECT Id FROM OpportunityLineItems)
             FROM Opportunities)
        FROM Account
        WHERE Id IN :changedAcctIds
    ]) {
        for (Opportunity o : a.Opportunities) {
            for (OpportunityLineItem oli : o.OpportunityLineItems) {
                linesToUpdate.add(new OpportunityLineItem(
                    Id = oli.Id,
                    Currency_Code__c = a.Currency_Code__c
                ));
            }
        }
    }

    if (!linesToUpdate.isEmpty()) update linesToUpdate;
}

The parent-to-child subquery does the entire join in one SOQL — far better than three sequential queries. The query counts as one against the 100-query governor limit.

When NOT to write a trigger

Before reaching for Apex, ask:

Use caseBetter tool
Cascade a value down to children onceCross-object formula field (Account.Name on Contact)
Update a roll-up on parentRoll-Up Summary field (if master-detail) or DLRS package
Simple “if parent X then update child Y”Record-triggered Flow
Anything bulk + multi-object + needs SOQLApex trigger (this pattern)

Cross-object formula fields don’t need DML at all — they compute on read. If the requirement is just “show the current parent name on the Contact,” use a formula and skip the trigger.

Anti-patterns

  • SOQL inside the for loopfor (Account a : Trigger.new) { List<Contact> cs = [SELECT ... WHERE AccountId = :a.Id]; }. Kills you at 100 parents.
  • DML inside the for loop — same problem at the DML governor (150 statements).
  • No change detection — every update on the parent (even unrelated fields) cascades to children. Wasteful and noisy.
  • before update instead of after update — in before, the parent’s new value isn’t yet committed, so child updates may read inconsistent state when chained logic queries the parent.
  • No recursion guard — child update fires Contact trigger fires Account update fires…
  • Querying every Contact in the org[SELECT Id, AccountId FROM Contact] without a WHERE is the all-time worst-case anti-pattern.

How to answer in 30 seconds

“After-update trigger on the parent. Use Trigger.oldMap to detect which records had the relevant field change. Pull all matching children in one SOQL filtered by WHERE AccountId IN :changedIds. Update them in one bulk DML. Guard against recursion with a static flag. Skip the trigger entirely if a cross-object formula field can do it.”

How to answer in 2 minutes

Walk the four-step pattern (detect change → bulk SOQL → mutate in memory → bulk DML), mention the parent-to-child sub-select for multi-level cascades, the recursion guard, and the “when to use a formula field instead” question. End with the bulkification numbers — 100 SOQL governor and 150 DML governor — so the interviewer knows you know the constraints.

Likely follow-up questions

  • How would this scale to 100,000 child records per parent?
  • What’s the difference between before and after triggers here?
  • When would you use a roll-up summary field instead?
  • What’s the SOQL governor limit and how does this query count?
  • How would you test this trigger?

Verified against: Apex Triggers — Bulk Design Patterns, SOQL Relationship Queries. Last reviewed 2026-05-19.