Top 15 Salesforce Apex Trigger Scenarios (Interview Questions) – Part 1

Welcome to Salesforce Hours!
If you’re looking to sharpen your Salesforce development skills with real-world examples and practical tutorials, you’re in the right place.

In this Part 1 of our “Salesforce Apex Trigger Scenarios“, we’re diving into some of the most common and sometimes tricky use cases you’ll come across when working with Apex triggers. These examples are built around standard Salesforce objects and designed to mirror the actual interview question that are asked in most of the Salesforce interview .

You’ll find clean, scalable Apex code solutions that not only solve problems but also follow best practices. Whether you’re just getting started or refining your skills, this guide is here to help you grow confidently as a Salesforce developer.

Scenario 1: Update Account Type When Opportunity is Closed Won

Requirement: When an Opportunity is marked as Closed Won, the related Account’s Type field should be updated to “Customer”.

Trigger Code

trigger OpportunityTrigger on Opportunity (after update) {
    if (TriggerHandlerUtility.isFirstRun) {
        if (Trigger.isAfter && Trigger.isUpdate) {
            OpportunityTriggerHandler.updateAccountType(Trigger.new, Trigger.oldMap);
        }
    }
}

Handler Class

public class OpportunityTriggerHandler {
    public static void updateAccountType(List<Opportunity> newList, Map<Id, Opportunity> oldMap) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opp : newList) {
            Opportunity oldOpp = oldMap.get(opp.Id);
            if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
                if (opp.AccountId != null) {
                    accountIds.add(opp.AccountId);
                }
            }
        }

        List<Account> accsToUpdate = [SELECT Id, Type FROM Account WHERE Id IN :accountIds];
        for (Account acc : accsToUpdate) {
            acc.Type = 'Customer';
        }
        if (!accsToUpdate.isEmpty()) {
            update accsToUpdate;
        }
    }
}

Recursion Utility

public class TriggerHandlerUtility {
    public static Boolean isFirstRun = true;
}

Scenario 2: Prevent Contact Creation Without Account

Requirement: Users should not be able to create a Contact without linking it to an Account.

Trigger Code

trigger ContactTrigger on Contact (before insert) {
    for (Contact con : Trigger.new) {
        if (con.AccountId == null) {
            con.addError('Please link this Contact to an Account before saving.');
        }
    }
}

Scenario 3: Roll Up Total Opportunity Amount to Account

Requirement: Automatically update the custom field Total_Opportunity_Amount__c on Account whenever Opportunities are added, updated, deleted, or undeleted.

Trigger Code

trigger OpportunityTrigger on Opportunity (after insert, after update, after delete, after undelete) {
    if (TriggerHandlerUtility.isFirstRun) {
        OpportunityTriggerHandler.rollupOpportunityAmount(
            Trigger.newMap,
            Trigger.oldMap,
            Trigger.isDelete
        );
    }
}

Handler Method

public class OpportunityTriggerHandler {
    public static void rollupOpportunityAmount(Map<Id, Opportunity> newMap, Map<Id, Opportunity> oldMap, Boolean isDelete) {
        Set<Id> accountIds = new Set<Id>();

        if (isDelete) {
            for (Opportunity opp : oldMap.values()) {
                if (opp.AccountId != null) accountIds.add(opp.AccountId);
            }
        } else {
            for (Opportunity opp : newMap.values()) {
                if (opp.AccountId != null) accountIds.add(opp.AccountId);
            }
        }

        Map<Id, Decimal> totalMap = new Map<Id, Decimal>();
        for (AggregateResult ar : [
            SELECT AccountId, SUM(Amount) totalAmount
            FROM Opportunity
            WHERE AccountId IN :accountIds
            GROUP BY AccountId
        ]) {
            totalMap.put((Id) ar.get('AccountId'), (Decimal) ar.get('totalAmount'));
        }

        List<Account> accountsToUpdate = new List<Account>();
        for (Id accId : accountIds) {
            Decimal total = totalMap.containsKey(accId) ? totalMap.get(accId) : 0;
            accountsToUpdate.add(new Account(Id = accId, Total_Opportunity_Amount__c = total));
        }

        update accountsToUpdate;
    }
}

Scenario 4: Clone OpportunityLineItems When Opportunity is Cloned

Requirement: When a user clones an Opportunity, also clone its associated OpportunityLineItems. A custom field Clone_Of__c tracks the original Opportunity.

Trigger Code

trigger OpportunityTrigger on Opportunity (after insert) {
    OpportunityTriggerHandler.cloneOpportunityLineItems(Trigger.new);
}

Handler Method

public class OpportunityTriggerHandler {
    public static void cloneOpportunityLineItems(List<Opportunity> newOpps) {
        Map<Id, Id> clonedMap = new Map<Id, Id>();
        for (Opportunity opp : newOpps) {
            if (opp.Clone_Of__c != null) {
                clonedMap.put(opp.Clone_Of__c, opp.Id);
            }
        }

        if (clonedMap.isEmpty()) return;

        List<OpportunityLineItem> newLines = new List<OpportunityLineItem>();
        for (OpportunityLineItem oli : [
            SELECT OpportunityId, Quantity, UnitPrice, PricebookEntryId
            FROM OpportunityLineItem
            WHERE OpportunityId IN :clonedMap.keySet()
        ]) {
            newLines.add(new OpportunityLineItem(
                OpportunityId = clonedMap.get(oli.OpportunityId),
                Quantity = oli.Quantity,
                UnitPrice = oli.UnitPrice,
                PricebookEntryId = oli.PricebookEntryId
            ));
        }

        insert newLines;
    }
}

Scenario 5: Count Contacts Under Each Account

Requirement: Update a custom field Contact_Count__c on Account whenever Contacts are inserted, deleted, or undeleted.

Trigger Code

trigger ContactTrigger on Contact (after insert, after delete, after undelete) {
    ContactTriggerHandler.updateContactCount(Trigger.newMap, Trigger.oldMap, Trigger.isDelete);
}

Handler Method

public class ContactTriggerHandler {
    public static void updateContactCount(Map<Id, Contact> newMap, Map<Id, Contact> oldMap, Boolean isDelete) {
        Set<Id> accountIds = new Set<Id>();

        if (isDelete) {
            for (Contact con : oldMap.values()) {
                if (con.AccountId != null) accountIds.add(con.AccountId);
            }
        } else {
            for (Contact con : newMap.values()) {
                if (con.AccountId != null) accountIds.add(con.AccountId);
            }
        }

        List<Account> accountsToUpdate = new List<Account>();
        for (AggregateResult ar : [
            SELECT AccountId, COUNT(Id) contactCount
            FROM Contact
            WHERE AccountId IN :accountIds
            GROUP BY AccountId
        ]) {
            accountsToUpdate.add(new Account(
                Id = (Id) ar.get('AccountId'),
                Contact_Count__c = (Integer) ar.get('contactCount')
            ));
        }

        update accountsToUpdate;
    }
}

Scenario 6: Update Account Rating for High-Value Opportunities

Requirement: When a new Opportunity with an Amount exceeding 100,000 is created, update the related Account’s Rating to “Hot”.

Trigger Code

trigger OpportunityTrigger on Opportunity (after insert) {
    OpportunityTriggerHandler.updateAccountRatingForHighValueOpps(Trigger.new);
}

Handler Method

public class OpportunityTriggerHandler {
    public static void updateAccountRatingForHighValueOpps(List<Opportunity> newOpps) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opp : newOpps) {
            if (opp.Amount != null && opp.Amount > 100000 && opp.AccountId != null) {
                accountIds.add(opp.AccountId);
            }
        }

        List<Account> accountsToUpdate = [SELECT Id, Rating FROM Account WHERE Id IN :accountIds];
        for (Account acc : accountsToUpdate) {
            acc.Rating = 'Hot';
        }
        if (!accountsToUpdate.isEmpty()) {
            update accountsToUpdate;
        }
    }
}

Scenario 7: Sync Primary Contact Phone to Account

Requirement: When a Contact is flagged as Primary__c = true, copy its Phone number to the parent Account record.

Trigger Code

trigger ContactTrigger on Contact (after insert, after update) {
    ContactTriggerHandler.syncPrimaryContactPhoneToAccount(Trigger.new);
}

Handler Method

public class ContactTriggerHandler {
    public static void syncPrimaryContactPhoneToAccount(List<Contact> contacts) {
        Map<Id, String> phoneMap = new Map<Id, String>();
        for (Contact con : contacts) {
            if (con.Primary__c && con.AccountId != null && con.Phone != null) {
                phoneMap.put(con.AccountId, con.Phone);
            }
        }
        List<Account> accs = [SELECT Id, Phone FROM Account WHERE Id IN :phoneMap.keySet()];
        for (Account acc : accs) {
            acc.Phone = phoneMap.get(acc.Id);
        }
        if (!accs.isEmpty()) update accs;
    }
}

Scenario 8: Display Open Case Count on Opportunity

Requirement : Whenever a Case is inserted, updated, deleted, or undeleted, update the related Opportunity’s custom field Open_Case_Count__c to reflect the current number of open cases.

Trigger Code

trigger CaseTrigger on Case (after insert, after update, after delete, after undelete) {
    CaseTriggerHandler.updateOpportunityCaseCount(Trigger.newMap, Trigger.oldMap, Trigger.isDelete);
}

Handler Method

public class CaseTriggerHandler {
    public static void updateOpportunityCaseCount(Map<Id, Case> newMap, Map<Id, Case> oldMap, Boolean isDelete) {
        Set<Id> oppIds = new Set<Id>();
        if (isDelete) {
            for (Case c : oldMap.values()) if (c.OpportunityId != null) oppIds.add(c.OpportunityId);
        } else {
            for (Case c : newMap.values()) if (c.OpportunityId != null) oppIds.add(c.OpportunityId);
        }

        Map<Id, Integer> countMap = new Map<Id, Integer>();
        for (AggregateResult ar : [
            SELECT OpportunityId, COUNT(Id) count FROM Case
            WHERE OpportunityId IN :oppIds AND Status != 'Closed'
            GROUP BY OpportunityId
        ]) {
            countMap.put((Id)ar.get('OpportunityId'), (Integer)ar.get('count'));
        }

        List<Opportunity> oppsToUpdate = new List<Opportunity>();
        for (Id oppId : oppIds) {
            Integer openCount = countMap.containsKey(oppId) ? countMap.get(oppId) : 0;
            oppsToUpdate.add(new Opportunity(Id = oppId, Open_Case_Count__c = openCount));
        }
        if (!oppsToUpdate.isEmpty()) update oppsToUpdate;
    }
}

Scenario 9: Copy Matched Contact Email to New Leads

Requirement: Upon Lead creation via web-to-lead, if a Contact exists with the same FirstName and LastName, copy that Contact’s Email into Matched_Contact_Email__c on the Lead.

Trigger Code

trigger LeadTrigger on Lead (before insert) {
    LeadTriggerHandler.matchAndSetContactEmail(Trigger.new);
}

Handler Method

public class LeadTriggerHandler {
    public static void matchAndSetContactEmail(List<Lead> leads) {
        Map<String, Lead> leadMap = new Map<String, Lead>();
        for (Lead l : leads) {
            if (l.FirstName != null && l.LastName != null) {
                leadMap.put(l.FirstName + ' ' + l.LastName, l);
            }
        }

        for (Contact c : [SELECT FirstName, LastName, Email FROM Contact WHERE FirstName != null AND LastName != null]) {
            String key = c.FirstName + ' ' + c.LastName;
            if (leadMap.containsKey(key)) {
                leadMap.get(key).Matched_Contact_Email__c = c.Email;
            }
        }
    }
}

Scenario 10: Append Industry Changes to Contact Descriptions

Requirement: When an Account’s Industry field is updated, append the new industry value to the Description of all related Contacts.

Trigger Code

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

Handler Method

public class AccountTriggerHandler {
    public static void appendIndustryToContactDescription(List<Account> accounts, Map<Id, Account> oldMap) {
        Set<Id> accountIds = new Set<Id>();
        for (Account acc : accounts) {
            Account oldAcc = oldMap.get(acc.Id);
            if (acc.Industry != oldAcc.Industry && acc.Industry != null) {
                accountIds.add(acc.Id);
            }
        }
        if (accountIds.isEmpty()) return;

        List<Contact> contactsToUpdate = [SELECT Id, Description, AccountId FROM Contact WHERE AccountId IN :accountIds];
        for (Contact c : contactsToUpdate) {
            String prefix = c.Description != null ? c.Description + ' | ' : '';
            c.Description = prefix + 'Industry changed to: ' + accountsMap.get(c.AccountId).Industry;
        }
        if (!contactsToUpdate.isEmpty()) update contactsToUpdate;
    }
}

Scenario 11: Prevent Changing Close Date on Closed Opportunities

Requirement: Once an Opportunity is marked as “Closed Won” or “Closed Lost,” the close date must remain locked.

trigger OpportunityTrigger on Opportunity (before update) {
    for (Opportunity opp : Trigger.new) {
        Opportunity oldOpp = Trigger.oldMap.get(opp.Id);

        if ((oldOpp.StageName == 'Closed Won' || oldOpp.StageName == 'Closed Lost') &&
            opp.CloseDate != oldOpp.CloseDate) {
            opp.CloseDate = oldOpp.CloseDate;
        }
    }
}

Scenario 12: Allow Only Managers to Update the Account Rating

Requirement: Only users whose title is “Manager” should be able to modify the “Rating” field on Account records.

trigger AccountTrigger on Account (before update) {
    User currentUser = [SELECT Title FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1];

    for (Account acc : Trigger.new) {
        Account oldAcc = Trigger.oldMap.get(acc.Id);

        if (acc.Rating != oldAcc.Rating && currentUser.Title != 'Manager') {
            acc.Rating = oldAcc.Rating;
        }
    }
}

Scenario 13: Block Contact Deletion If Related Opportunities Exist

Requirement: Prevent deletion of any Contact that is linked to at least one Opportunity.

trigger ContactTrigger on Contact (before delete) {
    Set<Id> contactIds = new Set<Id>();
    for (Contact con : Trigger.old) {
        contactIds.add(con.Id);
    }

    Map<Id, Integer> contactOppCount = new Map<Id, Integer>();
    for (AggregateResult ar : [
        SELECT ContactId, COUNT(Id) cnt FROM Opportunity
        WHERE ContactId IN :contactIds
        GROUP BY ContactId
    ]) {
        contactOppCount.put((Id)ar.get('ContactId'), (Integer)ar.get('cnt'));
    }

    for (Contact con : Trigger.old) {
        if (contactOppCount.containsKey(con.Id)) {
            con.addError('This contact cannot be deleted because related opportunities exist.');
        }
    }
}

Scenario 14: Reopen Case Automatically When Checkbox Is Checked

Requirement: If a custom checkbox field “Reopened__c” is checked, set the Case status to “Reopened.”

trigger CaseTrigger on Case (before update) {
    for (Case c : Trigger.new) {
        Case oldCase = Trigger.oldMap.get(c.Id);

        if (c.Reopened__c == true && oldCase.Reopened__c != true) {
            c.Status = 'Reopened';
        }
    }
}

Scenario 15: Prevent More Than One Primary Contact per Account

Requirement: Only one contact per Account should be marked as Primary__c = true, including new inserts or updates.

trigger ContactTrigger on Contact (before insert, before update) {
    Set<Id> accountIds = new Set<Id>();
    for (Contact con : Trigger.new) {
        if (con.Primary__c == true && con.AccountId != null) {
            accountIds.add(con.AccountId);
        }
    }

    Map<Id, Integer> primaryCountMap = new Map<Id, Integer>();
    for (AggregateResult ar : [
        SELECT AccountId, COUNT(Id) cnt FROM Contact
        WHERE Primary__c = true AND AccountId IN :accountIds
        GROUP BY AccountId
    ]) {
        primaryCountMap.put((Id)ar.get('AccountId'), (Integer)ar.get('cnt'));
    }

    // Simulate current transaction's changes
    Map<Id, Integer> batchPrimaryCount = new Map<Id, Integer>();
    for (Contact con : Trigger.new) {
        if (con.Primary__c == true && con.AccountId != null) {
            batchPrimaryCount.put(con.AccountId, batchPrimaryCount.getOrDefault(con.AccountId, 0) + 1);
        }
    }

    for (Contact con : Trigger.new) {
        if (con.Primary__c == true && con.AccountId != null) {
            Integer existingCount = primaryCountMap.getOrDefault(con.AccountId, 0);
            Integer currentCount = batchPrimaryCount.get(con.AccountId);
            if (existingCount + currentCount > 1) {
                con.addError('Only one primary contact is allowed per account.');
            }
        }
    }
}

Summary


We hope this guide strengthens your Salesforce skill set and prepares you for challenging development scenarios and interviews. Keep innovating, stay curious, and follow SalesforceHours for more in-depth tutorials, code snippets, and real-world best practices.
Also read out once about the Execution Governors and Limits of Apex to avoid any error.


Comment for PART 2


——————————————————————————————————————————————-
LATEST POST TO CHECKOUT

Leave a Comment