Mastering Triggers for Interviews
Trigger Execution Scenarios
● Insert: Before and After
● Update: Before and After
● Delete: Before and After
● Undelete: After
Trigger Context Variables
Trigger.isBefore - Boolean
Trigger.isAfter - Boolean
Trigger.isInsert - Boolean
Trigger.isUpdate - Boolean
Trigger.isDelete - Boolean
Trigger.IsUndelete - Boolean
Trigger.new - List
Trigger.Old - List
Trigger.newMap - Map
Trigger.OldMap- Map
Trigger.OperationType -Enum
Trigger.Size - Integer
Trigger.isExecuting - Boolean
Trigger Methods to Keep in Mind
1. Validation
2. Roll-up Summary
3. Before Insert
4. After Insert
5. Before Update
6. After Update
7. Before Delete
8. After Delete
9. Recursive Handler
10.Undelete: If Parent is Undeleted, Child is also Restored
Prepared By Manimegalai Dharmaraj
Event vs Context Variable
EVENT BEFORE AFTER
INSERT Trigger.New Trigger.New
Trigger.Newmap
UPDATE Trigger.New Trigger.New
Trigger.Newmap Trigger.Newmap
Trigger.Old Trigger.Old
Trigger.OldMap Trigger.OldMap
DELETE Trigger.Old Trigger.Old
Trigger.OldMap Trigger.OldMap
UNDELETE Trigger.New
Trigger.Newmap
How to Decide Which Event to Choose
Action to Perform Event DML Required
Before After
Field Update on the Y
same record
Add Validation error Y Y
Field Update on the Y Y
related record
Create New Y Y
Record(Related/Non
Related)
Sending Y
Email/Custom
Notification
Call out to external
system
Prepared By Manimegalai Dharmaraj
Flow vs. Apex Trigger
When to Use Flow:
1. Simple Automation: For straightforward processes, like sending notifications,
updating fields, or creating records based on specific criteria.
2. User Interaction: When you need a screen-based interaction, such as a form for
users to fill out.
3. Declarative Solution: When the task can be accomplished using Salesforce’s
declarative tools, making it easier to maintain and less technical.
4. Low Volume Updates: For single or low-volume record updates, where bulk
processing isn’t a requirement.
Example: Use Flow to automatically send a welcome email to a new Contact after
they’re created.
When to Use Trigger:
1. Complex Logic: When you need advanced logic that flows can’t handle, such as
multiple conditional checks, loops, or calculations across related objects.
2. Bulk Processing: For handling bulk updates or inserts, where large amounts of
records need processing at once (e.g., data migrations or mass updates).
3. Cross-Object Actions: When you need to update records on unrelated objects
or perform actions on objects not directly related to each other.
4. Real-Time Processing: For faster, real-time processing where performance is
critical.
Example: Use a Trigger to automatically update an Account’s Last_Purchase_Date
whenever an Opportunity related to that Account is closed as "Won."
Points to Keep in Mind
Trigger Chunk Size
● Triggers process records in chunks of 200.
● Example: If 210 records are inserted, the trigger runs twice—processing the first 200
records, then the remaining 10
Avoiding Recursive Triggers
Recursive triggers are tricky—they can lead to max trigger depth errors due to repeated
execution.
Prepared By Manimegalai Dharmaraj
Prevent Recursion Using:
1. Static Variables
2. Static Set of IDs (better for handling more than 200 records)
Static Variables vs. Static Sets in Triggers
When trying to prevent recursive triggers, many developers use static variables. However,
using static variables alone can lead to missed records in scenarios where you’re processing
more than 200 records.
Here's Why:
● With a static variable, only the first 200 records are processed.
● The additional records (like the remaining 10 in our example) won’t be processed, and no
error will be thrown, which can lead to incomplete processing!
Solution: Use a Static Set of IDs
Using a Static Set of IDs ensures that:
1. All records in chunks are processed, preventing any missed actions.
2. Remaining records after the first 200 are handled without the risk of unprocessed data.
Test-Driven Development (TDD)
● Use TDD to validate bulkified records!
● Create 500 test records and watch as they’re deleted after testing by default.
Common Errors
● Maximum Trigger Depth Exceeded: Occurs if recursion isn’t controlled.
○ Example: If “Customer Priority” is set to High, and you set “Rating” to Hot in the
same After Trigger—expect a recursion error!
Before vs. After Triggers
● Before Trigger: Great for validations, updating the same record, or adding errors before
DML operations.
○ Examples:
■ Validate data on insert/update
■ Prevent delete if criteria aren’t met
● After Trigger: Ideal for creating related records, sending emails, or logging activities
where context variables are read-only.
○ Examples:
■ Create a Contact when an Account is created
■ Send an email when an Opportunity is created
Roll-Up Summaries for Lookups
1. Roll up counts, sums, averages, or other aggregates to the parent.
2. Aggregate child records and update parent values for insights.
Prepared By Manimegalai Dharmaraj
Note: Post-undeletion automation isn’t supported in Flows.
Additional Notes
● System Validation: Fields like Name, Mobile, Email have character limits.
● Synchronous Execution: Triggers process records in a batch size of 200.
● External Callouts: Use Future methods for callouts as triggers are synchronous.
Best Practice
1. One Trigger per Object (Control execution order)
2. Implement business logic in a Trigger Handler
3. Always bulkify for single/multiple records
4. Avoid nested loops ➡ Use Maps instead
5. Control recursion carefully
6. Document code thoroughly
7. Before deployment, remove unused code and debug statements
8. Unit Test with bulk scenarios using test classes
9. Master Trigger Context Variables (like Trigger.New, Trigger.Old, etc.)
Adding Try-Catch for Error Handling
Using a try-catch block in the Trigger Handler methods can help catch unexpected errors, log
them for troubleshooting, and ensure the code doesn’t fail silently
Here's a more structured version of the syntax and example code for a Trigger
Handler in Salesforce, with comments to guide through each part:
Apex Trigger Handler Pattern
The Trigger Handler pattern helps in organizing the trigger code efficiently, allowing for clean
and manageable logic. Below is the syntax to create a handler class and a trigger on the Account
object.
Apex Trigger Handler Class
public class AccountTriggerHandler {
// Declare variables for the current and old records
List<Account> triggerNew;
List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;
// Constructor to initialize variables with trigger context
public AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New;
triggerOld = (List<Account>) Trigger.Old;
triggerNewMap = (Map<Id, Account>) Trigger.NewMap;
Prepared By Manimegalai Dharmaraj
triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}
// Main method to handle actions based on the trigger operation type
public void doAction() {
// Switch statement for handling different trigger operations
switch on Trigger.operationType {
when BEFORE_INSERT {
onBeforeInsert();
}
when AFTER_INSERT {
onAfterInsert();
}
when BEFORE_UPDATE {
onBeforeUpdate();
}
when AFTER_UPDATE {
onAfterUpdate();
}
when BEFORE_DELETE {
onBeforeDelete();
}
when AFTER_DELETE {
onAfterDelete();
}
when AFTER_UNDELETE {
onAfterUndelete();
}
}
}
// Define methods for each trigger event as needed
public void onBeforeInsert() {
// Logic for before insert
}
public void onAfterInsert() {
// Logic for after insert
}
public void onBeforeUpdate() {
// Logic for before update
}
public void onAfterUpdate() {
// Logic for after update
}
Prepared By Manimegalai Dharmaraj
public void onBeforeDelete() {
// Logic for before delete
}
public void onAfterDelete() {
// Logic for after delete
}
public void onAfterUndelete() {
// Logic for after undelete
}
}
This trigger references the AccountTriggerHandler class to execute actions based on the
specified events.
Apex Trigger
trigger AccountTrigger on Account (before insert, after
insert, before update, after update, before delete, after
delete, after undelete) {
// Initialize the handler and call the action method
AccountTriggerHandler handler = new AccountTriggerHandler();
handler.doAction();
}
Notes on Trigger Handler Pattern
● Benefits: This pattern keeps your code modular, making it easier to maintain and scale. It
also allows for better error handling and improved readability.
● Bulkification: Ensure that each method inside the handler class processes records in bulk
to prevent hitting governor limits.
● Trigger Operations: Use Trigger.operationType to determine the context in which the
trigger is running (insert, update, delete, etc.).
● Testing: For each method in the handler, write comprehensive test classes covering
different scenarios to ensure that your triggers run smoothly with bulk data.
Prepared By Manimegalai Dharmaraj
Examples For Each Method
Before Insert
Scenario: Set rating hot when account created with industry as banking.
Apex - Trigger handler
public class AccountTriggerHandler {
// Declare the Variables
List<Account> triggerNew;
List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;
// Constructor to initialize the variables
public AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New;
triggerOld = (List<Account>) Trigger.Old;
triggerNewMap = (Map<Id, Account>) Trigger.NewMap;
triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}
// Action based on trigger operation type
public void doAction()
{
// Switch statement to handle different trigger operations
Switch on Trigger.operationType
{
When BEFORE_INSERT
{
onBeforeInsert();
}
}
}
// Logic for before insert operation
public void onBeforeInsert() {
updateRating();
}
Prepared By Manimegalai Dharmaraj
// Method to update the rating to 'Hot' if the account industry is
'Banking'
public void updateRating() {
for (Account record : triggerNew) {
if (record.Industry == 'Banking') {
record.Rating = 'Hot';
}
}
}
}
Apex Trigger
trigger AccountTrigger on Account (before insert) {
AccountTriggerHandler handler = new AccountTriggerHandler();
handler.doAction();
}
Here are the best practices followed in the above code, each explained in a single line:
Use of Switch: The Switch on Trigger.operationType structure is used to determine which
operation the trigger is running on. In this case, BEFORE_UPDATE is handled with the
onBeforeUpdate() method.
1. Bulkification: The code is designed to handle multiple records efficiently in a single
transaction.
2. Trigger Handler Pattern: Business logic is moved to a separate handler class for cleaner
separation and maintainability.
3. Minimal Logic in Trigger: The trigger only delegates work to the handler class, keeping
the trigger lightweight.
4. Switch-Based Control Flow: The use of the Switch makes it easy to add more
operations (like BEFORE_INSERT, AFTER_UPDATE, etc.) in the future, enhancing
code flexibility and readability.
5. Proper Constructor: The handler class has a correctly defined constructor for variable
initialization.
6. Reusability: Each operation (e.g., updateRating()) is in a separate method, making it easy
to extend or modify.
7. Null Checks Avoided: Fields are directly accessed as the conditions ensure valid entries,
improving code efficiency.
Prepared By Manimegalai Dharmaraj
Before Update
Scenario:When a user creates an account record without rating then
prepopulates the rating as cold.
Apex Trigger Handler
public class AccountTriggerHandler {
// Declare the Variables
List<Account> triggerNew;
List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;
// Constructor to initialize the variables
public AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New;
triggerOld = (List<Account>) Trigger.Old;
triggerNewMap = (Map<Id, Account>) Trigger.NewMap;
triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}
// Action based on Trigger Operation Type
public void doAction() {
// Switch statement to handle different trigger operations
Switch on Trigger.operationType {
when BEFORE_UPDATE {
onBeforeUpdate();
}
}
}
// Logic for Before Update Operation
public void onBeforeUpdate() {
updateRating();
}
// Method to update the Rating as 'Cold' if the Rating field is
empty
public void updateRating() {
for (Account record : triggerNew) {
if (record.Rating == null) {
record.Rating = 'Cold';
}
}
}
}
Prepared By Manimegalai Dharmaraj
Trigger
trigger AccountTrigger on Account (before update) {
AccountTriggerHandler handler = new AccountTriggerHandler();
handler.doAction();
}
Before Delete
Scenario: Leads with a status of 'Working Contacted' should not be deleted
under any circumstances.
Apex Trigger Handler
public class LeadTriggerHandler {
// Declare the Variables
List<Lead> triggerOld;
Map<Id, Lead> triggerOldMap;
// Constructor to initialize the variables
public LeadTriggerHandler() {
triggerOld = (List<Lead>) Trigger.Old;
triggerOldMap = (Map<Id, Lead>) Trigger.OldMap;
}
// Action based on trigger operation type
public void doAction() {
// Switch statement to handle trigger operation types
Switch on Trigger.operationType {
when BEFORE_DELETE {
onBeforeDelete();
}
}
}
// Logic for Before Delete Operation
public void onBeforeDelete() {
beforeDeleteValidation();
}
Prepared By Manimegalai Dharmaraj
// Method to prevent deletion of leads with 'Working Contacted'
status
public void beforeDeleteValidation() {
for (Lead record : triggerOld) {
if (record.Status == 'Working - Contacted') {
record.addError('Lead cannot be deleted when the
status is Working Contacted.');
}
}
}
}
Trigger
trigger LeadTrigger on Lead (before delete) {
LeadTriggerHandler handler = new LeadTriggerHandler();
handler.doAction();
}
After Insert
Scenario: When a Opportunity Created Create a Task for Follow up
Apex Trigger Handler
public class OpportunityHandler {
List<Opportunity> triggerNew;
List<Opportunity> triggerOld;
Map<Id, Opportunity> triggerNewMap;
Map<Id, Opportunity> triggerOldMap;
// Constructor to initialize trigger context variables
public OpportunityHandler() {
this.triggerNew = (List<Opportunity>) Trigger.New;
this.triggerOld = (List<Opportunity>) Trigger.Old;
this.triggerNewMap = (Map<Id, Opportunity>) Trigger.NewMap;
this.triggerOldMap = (Map<Id, Opportunity>) Trigger.OldMap;
}
// Main action handler
public void doAction() {
switch on Trigger.operationType {
when AFTER_INSERT {
Prepared By Manimegalai Dharmaraj
onAfterInsert();
}
}
}
// Method to create follow-up tasks after an Opportunity is
inserted
public void onAfterInsert() {
List<Task> taskList = new List<Task>();
// Query the Opportunities inserted
List<Opportunity> opportunityList = [SELECT Id, StageName,
OwnerId FROM Opportunity WHERE Id IN :triggerNew];
// Loop through each Opportunity and create a Task for follow-up
for (Opportunity record : opportunityList) {
Task taskRecord = new Task();
taskRecord.WhatId = record.Id;
taskRecord.OwnerId = record.OwnerId; // Owner of the
Opportunity
taskRecord.Subject = 'Follow up';
taskList.add(taskRecord);
}
// Insert the Task list with error handling
try {
insert taskList;
} catch (DmlException e) {
// Log the exception or handle it as needed
System.debug('Error while creating tasks: ' +
e.getMessage());
}
}
}
Trigger
trigger OpportunityTrigger on Opportunity (after insert, before
update, after update) {
OpportunityHandler obj = new OpportunityHandler();
obj.doAction();
}
Prepared By Manimegalai Dharmaraj
After Update
Scenario: When an account is marked as inactive then close all the cases if any.
Apex Trigger Handler
public class AccountHandler {
List<Account> triggerNew;
List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;
// Constructor to initialize trigger context variables
public AccountHandler() {
this.triggerNew = (List<Account>) Trigger.New;
this.triggerOld = (List<Account>) Trigger.Old;
this.triggerNewMap = (Map<Id, Account>) Trigger.NewMap;
this.triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}
// Main action handler
public void doAction() {
switch on Trigger.operationType {
when AFTER_UPDATE {
onAfterUpdate();
}
}
}
// Method to close all cases when an account is marked as
inactive
public void onAfterUpdate() {
Set<Id> inactiveAccountIds = new Set<Id>();
// Collect all account IDs where Active__c changed to 'No'
for (Account acc : triggerNew) {
Account oldAcc = triggerOldMap.get(acc.Id);
if (oldAcc.Active__c != 'No' && acc.Active__c == 'No') {
inactiveAccountIds.add(acc.Id);
}
}
// Query all open cases related to the inactive accounts
List<Case> caseList = new List<Case>();
if (!inactiveAccountIds.isEmpty()) {
List<Case> relatedCases = [SELECT Id, Status, AccountId
FROM Case
WHERE
AccountId IN :inactiveAccountIds
AND Status != 'Closed'];
Prepared By Manimegalai Dharmaraj
// Set each case status to 'Closed'
for (Case caseRecord : relatedCases) {
caseRecord.Status = 'Closed';
caseList.add(caseRecord);
}
}
// Perform DML with error handling
if (!caseList.isEmpty()) {
try {
update caseList;
}
catch (DmlException e) {
System.debug('Error while updating cases: ' +
e.getMessage());
}
}
}
}
Trigger
trigger AccountTrigger on Account (after update) {
AccountHandler obj = new AccountHandler();
obj.doAction();
}
After Delete
Scenario: When a Department record is deleted, its related Employee records
should also be deleted.
Apex Trigger Handler
public class DepartmentTriggerHandler {
List<Department__c> triggerNew;
List<Department__c> triggerOld;
Map<Id, Department__c> triggerNewMap;
Map<Id, Department__c> triggerOldMap;
// Static list to store related employee records for cascading
Prepared By Manimegalai Dharmaraj
delete
static List<Employee_Management__c> employeeList = new
List<Employee_Management__c>();
// Constructor to initialize trigger context variables
public DepartmentTriggerHandler() {
triggerNew = (List<Department__c>) Trigger.New;
triggerOld = (List<Department__c>) Trigger.Old;
triggerNewMap = (Map<Id, Department__c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department__c>) Trigger.OldMap;
}
// Main action handler
public void doAction() {
switch on Trigger.operationType {
when BEFORE_DELETE {
onBeforeDelete();
}
when AFTER_DELETE {
onAfterDelete();
}
}
}
// Method to collect employee records related to departments
marked for deletion
public void onBeforeDelete() {
Set<Id> departmentIds = new Set<Id>();
// Collect all IDs of departments being deleted
for (Department__c dept : triggerOld) {
departmentIds.add(dept.Id);
}
// Query related employees whose department is in the list
of department IDs being deleted
employeeList = [SELECT Id, Name FROM Employee_Management__c
WHERE Departments__c IN :departmentIds];
}
// Method to delete collected employee records
public void onAfterDelete() {
if (!employeeList.isEmpty()) {
try {
delete employeeList;
}
Prepared By Manimegalai Dharmaraj
catch (DmlException e) {
System.debug('Error deleting employees: ' +
e.getMessage());
}
}
}
}
Trigger
trigger DepartmentTrigger on Department__c (before delete, after
delete) {
DepartmentTriggerHandler handler = new
DepartmentTriggerHandler();
handler.doAction();
}
Note: Using static for employee List allows the variable to be shared consistently within the
same transaction, preserving data integrity across trigger executions, preventing redundant
processing, and safeguarding against unintended modifications. This approach is particularly
valuable in Salesforce’s bulk processing environment, where efficiency and adherence to
governor limits are critical.
Undelete
Scenario: When a Department record is undeleted, restore the related
Employee records as well.
Apex Trigger Handler
public class DepartmentTriggerHandler {
List<Department__c> triggerNew;
List<Department__c> triggerOld;
Map<Id, Department__c> triggerNewMap;
Map<Id, Department__c> triggerOldMap;
// Static list to store related employee records for cascading
delete
static List<Employee_Management__c> employeeList = new
List<Employee_Management__c>();
Prepared By Manimegalai Dharmaraj
// Constructor to initialize trigger context variables
public DepartmentTriggerHandler() {
triggerNew = (List<Department__c>) Trigger.New;
triggerOld = (List<Department__c>) Trigger.Old;
triggerNewMap = (Map<Id, Department__c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department__c>) Trigger.OldMap;
// Main action handler
public void doAction() {
switch on Trigger.operationType {
when AFTER_UNDELETE {
onAfterUnDelete();
}
}
}
public void onAfterUndelete() {
List<Department__c> undeletedDepartments = Trigger.new;
// Fetch backup records related to the undeleted Department
records
List<Deleted_Employee_Backup__c> backupRecords = [
SELECT DepartmentId__c, EmployeeName__c,
EmployeeTitle__c
FROM Deleted_Employee_Backup__c
WHERE DepartmentId__c IN :undeletedDepartments
];
// List to hold recreated Employee records
List<Employee_Management__c> employeesToRestore = new
List<Employee_Management__c>();
// Loop through backup records and recreate Employee records
for (Deleted_Employee_Backup__c backup : backupRecords) {
Employee_Management__c employee = new Employee_Management__c();
employee.Department__c = backup.DepartmentId__c;
employee.Name = backup.EmployeeName__c;
employee.Title__c = backup.EmployeeTitle__c;
// Add other fields as needed from backup
employeesToRestore.add(employee);
}
// Insert recreated Employee records and handle any errors
try {
if (!employeesToRestore.isEmpty()) {
Prepared By Manimegalai Dharmaraj
insert employeesToRestore;
}
} catch (DmlException e) {
System.debug('Exception while restoring employees: ' +
e.getMessage());
}
}
}
Trigger
trigger DepartmentTrigger on Department__c (after undelete) {
DepartmentTriggerHandler handler = new
DepartmentTriggerHandler();
handler.onAfterUndelete();
}
Notes
● Backup Object: This requires a custom object, Deleted_Contact_Backup__c, where each
deleted Contact record is stored when its Account is deleted. Make sure the backup
records are created at the time of deletion to allow restoration.
● Cleanup: After restoring, you may want to delete the backup records related to the
restored Contacts.
Another way
Apex Trigger Handler
public class DepartmentTriggerHandler {
List<Department__c> triggerNew;
List<Department__c> triggerOld;
Map<Id, Department__c> triggerNewMap;
Map<Id, Department__c> triggerOldMap;
// Static list to store related employee records for cascading
delete
static List<Employee_Management__c> employeeList = new
List<Employee_Management__c>();
// Constructor to initialize trigger context variables
public DepartmentTriggerHandler() {
triggerNew = (List<Department__c>) Trigger.New;
triggerOld = (List<Department__c>) Trigger.Old;
triggerNewMap = (Map<Id, Department__c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department__c>) Trigger.OldMap;
Prepared By Manimegalai Dharmaraj
// Main action handler
public void doAction() {
switch on Trigger.operationType {
when AFTER_UNDELETE {
onAfterUnDelete();
}
}
}
public void onAfterUnDelete() {
// Query Employee records related to undeleted Departments in the
Recycle Bin
List<Employee_Management__c> empList = [
SELECT Id, Department__c
FROM Employee_Management__c
WHERE Department__c IN :Trigger.new AND IsDeleted =
true ALL ROWS
];
try {
// If there are deleted Employee records, undelete them
if (!empList.isEmpty()) {
undelete empList;
}
} catch (DmlException e) {
System.debug('Exception while undeleting employees: '
+ e.getMessage());
}
}
}
Trigger
trigger DepartmentTrigger on Department__c (after undelete) {
DepartmentTriggerHandler handler = new
DepartmentTriggerHandler();
handler.onAfterUnDelete();
}
Prepared By Manimegalai Dharmaraj
Rollup Summary
Scenario: When Opportunity stage changed into negotiation/review then update the
opportunities count in the Account Object
Apex Trigger Handler
public class OpportunityTriggerHandler {
List<Opportunity> triggerNew;
List<Opportunity> triggerOld;
Map<Id, Opportunity> triggerNewMap;
Map<Id, Opportunity> triggerOldMap;
// Constructor to initialize trigger context variables
public DepartmentTriggerHandler() {
triggerNew = (List<Opportunity>) Trigger.New;
triggerOld = (List<Opportunity>) Trigger.Old;
triggerNewMap = (Map<Id, Opportunity>) Trigger.NewMap;
triggerOldMap = (Map<Id, Opportunity>) Trigger.OldMap;
}
// Main action handler
public void doAction() {
Switch on Trigger.OperationType
{
When AFTER_INSERT
{
updateCountOfOpportunity();
}
}
}
public static void updateCountOfOpportunity()
{
//1. Collect Unique Parent Ids
Set<Id> accountIds=new Set<Id>();
for(Opportunity oppRecord:triggernew)
{
if(oppRecord.AccountId!=null)
{
//1. update - Compare Old and new values
//2. Insert, Delete, Undelete - No need to compare
if(Trigger.isupdate)
{
if(Trigger.OldMap.get(oppRecord.Id).StageName!=oppRecord.StageName)
accountIds.add(oppRecord.AccountId);
}
Prepared By Manimegalai Dharmaraj
else
{
accountIds.add(oppRecord.AccountId);
}
}
}
//2.Count - Aggregate Function
List<AggregateResult> opportunityResult=[Select Count(Id)
numberOfOpportunities,
AccountId FROM Opportunity
Where StageName =:Label.OpportunityStageNameChange
and AccountId in:accountIds
Group By AccountId];
];
//3.Update Parent Account With Opportunity Count
//Account Id, Count Opportunity
//It Should be SObject record
//Construct Account record from Aggregate Result
List<Account> accountList=new List<Account>();
for(AggregateResult opp:opportunityResult)
{
Account accountRecord=new Account();//Empty record in
memory
accountRecord.Id=(Id)opp.get('AccountId');//Object--Id
accountRecord.OpportuniyCount__c=(Decimal)opp.get('numberOfOpportunitie
s');
accountList.add(accountRecord);
}
// 4. Update the Account records
if (!accountList.isEmpty()) {
try {
update accountList;
} catch (DmlException e) {
System.debug('Error updating Account records: ' +
e.getMessage());
}
}
}
}
Prepared By Manimegalai Dharmaraj
Trigger
trigger OpportunityTrigger on Opportunity (after insert, after
update, after delete, after undelete)
{
OpportunityTriggerHandler handler=new
OpportunityTriggerHandler();
handler.doAction();
}
Recursive Handler
Scenario Overview:
You have the following set up:
1. Flow: Automatically sets Rating = Hot on an Account when Customer Priority = High.
2. Before Update Trigger: Checks if the Account Type is updated to Customer - Direct. If
true, it sets Customer Priority = High.
3. After Update Trigger: Creates a follow-up task for the Account if certain criteria are
met.
Execution Flow and Recursion:
Given Salesforce’s order of execution, here’s how this scenario unfolds:
1. Initial Update to Account Record: An update to the Account record occurs (e.g.,
changing Type to Customer - Direct).
2. Before Update Trigger (First Run):
○ The trigger detects that Type is set to Customer - Direct, so it sets Customer
Priority = High.
3. After Update Trigger (First Run):
○ Since Customer Priority is now High, the After Update Trigger creates a follow-up
task for this Account.
4. Flow (After-Save):
○ The Flow detects Customer Priority = High and sets Rating = Hot on the Account.
5. Before and After Triggers Execute Again:
○ The Flow’s update to Rating = Hot causes Salesforce to re-run the Before Update
and After Update triggers for this record.
○ The Before Update Trigger might again try to set Customer Priority = High, and
the After Update Trigger might attempt to create another follow-up task.
6. Infinite Loop:
○ The combination of the Flow update and the triggers can create a loop where
Salesforce repeatedly updates the Account and creates tasks in an endless cycle.
Solution: Avoiding Recursion with a Recursive Control Mechanism
Prepared By Manimegalai Dharmaraj
To prevent this looping behavior, we use a recursive check. This mechanism ensures that the
triggers only execute certain actions (like creating tasks) once per transaction.
Recursive Handler
Public class AccountRecursiveCheck {
Public static Set<Id> setIds = new Set<Id>();
}
Trigger
trigger AccountTrigger on Account (before update, after update)
{
// Before Update Trigger: Set Customer Priority for specific
account types
if (trigger.isBefore && trigger.isUpdate) {
for (Account acc : Trigger.new) {
if (acc.Type == 'Customer - Direct') {
acc.CustomerPriority__c = 'High';
}
}
}
// After Update Trigger: Create follow-up Task if criteria are
met and avoid recursion
if (trigger.isAfter && trigger.isUpdate) {
List<Task> taskList = new List<Task>();
for (Account acc : Trigger.new) {
// Check if the Account ID has already been processed
if (!AccountRecursiveCheck.setIds.contains(acc.Id)) {
AccountRecursiveCheck.setIds.add(acc.Id);
// Create a follow-up Task for each Account not previously processed
Task taskObj = new Task();
taskObj.WhatId = acc.Id;
taskObj.Subject = 'Followup';
taskList.add(taskObj);
}
}
// Insert Tasks if there are any to insert
if (!taskList.isEmpty()) {
insert taskList;
}
// Clear the set of processed Account IDs after completion
AccountRecursiveCheck.setIds.clear();
Prepared By Manimegalai Dharmaraj
}
}
Interview Questions
1. What is a Salesforce trigger?
A Salesforce trigger is a component of Apex code that runs before or after specified data
manipulation language (DML) events, such as before object records are inserted into the
database, even after records are deleted, and so on. Triggers are used to execute custom actions
before or following changes to Salesforce records.
2. What is a Trigger Code?
A Trigger code in Salesforce is an element of Apex code that runs before or after particular data
manipulation language (DML) events. These events include Salesforce records insert, update,
deletes, and undelete. The trigger code is used to implement custom logic, such as data
validation, automation, or modification, in response to various DML events.
3. What are the types of Salesforce Triggers?
There are two types of Triggers
Before Triggers: These are called before the DML process on the database is completed. They
are commonly used to validate or change data before it is saved.
After Triggers: These triggers are executed after the DML operation and data temporarily
saved into the database. They can be used when accessing system-set field values (such as a
recordId or LastModifiedDate field) or modifying other documents based on the initial record’s
actions.
4. Can you explain the trigger execution order in
Salesforce?
Salesforce executes the following in order:
● Load / Initialize Record
● System Validations
● Before Trigger
Prepared By Manimegalai Dharmaraj
● Custom Validations
● Duplicate Rules
● Save / No Commit
● After Trigger
● Assignment Rules
● Auto-Response Rules
● Workflow Actions
● Update Record
● System Validations (re-validation occurs here)
● Processes and Flows
● Escalation Rules
● Entitlement Rules
● Parent Roll-up Summaries
● Grandparent Roll-up Summaries
● Criteria-Based Sharing
● Database Commit
● After Commit Logic
5. What is the Trigger.new and Trigger.old context
variable?
Trigger New: It holds the list of new records to be inserted or updated.
Trigger Old: It has the list of old records values before they were updated or deleted.
6. Can triggers be bulkified in Salesforce?
Yes, triggers should always be written with bulk processing in mind, meaning they should
handle multiple records at once. Using loops or SOQL Queries inside loops can cause issues
with governor Limits so developers need to optimize triggers for bulk operations.
7. What are recursive triggers, and how can you avoid
them?
A recursive trigger shows up when a trigger calls itself, that leads to an infinite loop. You can
avoid recursion by using a static boolean variable to track whether the Trigger has already run.
Prepared By Manimegalai Dharmaraj
8. What is the use of Trigger.isExecuting?
Trigger.isExecuting is a boolean that returns true if the current context is a trigger, that will
eventually help to check whether your code is running in a trigger context.
9. State the difference between Trigger.new and
Trigger.newMap.
Trigger.new is a list of records with new values. On the other hand, Trigger.New.Map is a map
of IDs to records. This is useful when accessing records using their IDs for processing.
10. Can you use DML operations in triggers?
Yes, you can use DML operations in triggers. However, the best practice is to limit the use of
DML operations to avoid hitting Salesforce governor limits. Using collections to handle bulk
records in DML is recommended.
11. How would you stop a Trigger from executing
multiple times?
You can utilize static variables to prevent a trigger from being executed more than once. A static
variable serves as a flag. You can set this flag after the Trigger has been executed. You can skip
the logic on subsequent recursive calls to the Trigger by checking the flag’s value.
This process ensures that the Trigger is not executed repeatedly within the same transaction,
preventing undesirable behavior or failures.
13. How do you ensure that your Triggers are bulk-safe?
Avoid performing DML actions (insert, update, and delete) or SOQL searches within loops to
ensure your triggers are bulk-safe. This can soon exceed Salesforce governor restrictions,
mainly when dealing with many records. Alternatively, you should:
● Collect records or data into collections (like lists or maps).
● Perform DML or query operations on the entire collection outside of the loop.
So, Trigger can handle mass activities efficiently without experiencing performance difficulties.
Prepared By Manimegalai Dharmaraj
14. What are context variables in Salesforce Triggers?
context variables in Salesforce Triggers give critical information about the state of the records
being processed and the runtime context in which the Trigger is executed. Some standard
context variables are:
Trigger. New: Contains the most recent versions of the records inserted or changed.
Trigger. Old: Contains previous versions of the records being updated or destroyed.
Trigger.isInsert, Trigger.isUpdate, and Trigger.Delete: Indicate the type of DML activity that
prompted the Trigger to run.
Trigger.isBefore, Trigger.isAfter: Determine whether the Trigger executes before or after the
DML action.
These variables enable developers to handle multiple scenarios efficiently within a single
Trigger.
15. Can you control multiple Triggers for the same
object?
Salesforce platform permits you to have numerous Triggers on the same object. However, the
hierarchy in which the various triggers are executed is not guaranteed. This can lead to
unpredictability in how the triggers interact.
Salesforce recommends that each item have only one Trigger to avoid potential complications.
A Trigger Handler class allows you to regulate the sequence and execution of your logic easily.
16. How do you test Triggers in Salesforce?
To test Triggers in Salesforce, you write test classes and methods. In the test methods, you
create test data and perform DML operations that invoke the Trigger. You also use
System.assert methods to verify the Trigger’s behavior.
17. How can you call a batch class from a Trigger?
While it’s not recommended due to potential governor limit issues, you can call a batch class
from a Trigger using the Database.executeBatch method. A better practice is to use a queueable
or future method to handle asynchronous processing.
Prepared By Manimegalai Dharmaraj
18. When would you use a Trigger instead of Workflow,
Process Builder, or Flows?
You would use a Trigger instead of Workflow, Process Builder, or Flows when dealing with
complex business logic that cannot be handled by these declarative tools or when you need to
create or manipulate records related to the one being processed.
19. How can you handle exceptions in Salesforce
Triggers?
Exceptions in Salesforce Triggers can be handled using try-catch blocks. In the try block, you
write the code which might throw an exception, and in the catch block, you handle the
exception. This can involve adding an error message to the record or logging the error for
review.
20. What is the purpose of a Trigger handler class?
The purpose of a Trigger handler class is to separate the logic of your Trigger from the Trigger
itself. This separation leads to code that is easier to maintain and test. The handler class contains
the methods that carry out the operations needed when the Trigger fires.
21. What happens if a Trigger causes a runtime
exception?
If a Trigger causes a runtime exception, the entire transaction is rolled back. This includes all
DML operations and changes in governor limit usage.
22. How do you debug a Trigger in Salesforce?
To debug a Trigger in Salesforce, you can use debug logs. You can add System.debug
statements in your Trigger code, and then check the logs after executing the operations that fire
the Trigger.
Prepared By Manimegalai Dharmaraj
23. What are the limitations of using Triggers in
Salesforce?
Triggers in Salesforce are subject to several limitations:
● Triggers are not suitable for time-consuming operations as they can hit governor limits.
● They can lead to recursion if not handled properly.
● Triggers run on all records of a batch operation, so selective processing can be
challenging.
● Debugging Triggers can be difficult, particularly in production.
● Overutilization of Triggers can lead to complex order of execution scenarios.
24. Can you use SOQL queries in a Trigger? What are
the best practices?
Yes, SOQL queries can be used in a Trigger, but it’s important to follow best practices to avoid
hitting governor limits. These include bulkifying the Trigger to handle multiple records
efficiently, avoiding SOQL queries inside loops, and ensuring that the total number of SOQL
queries does not exceed the limit in a single transaction. Using collections to store data and
querying outside of loops is recommended
Prepared By Manimegalai Dharmaraj