Trigger Bulkification and Best Practices
Trigger Bulkification and Best Practices is a critical concept in Salesforce development that ensures triggers handle multiple records efficiently rather than processing one record at a time. Since Salesforce operations can involve up to 200 records in a single DML transaction, triggers must be des… Trigger Bulkification and Best Practices is a critical concept in Salesforce development that ensures triggers handle multiple records efficiently rather than processing one record at a time. Since Salesforce operations can involve up to 200 records in a single DML transaction, triggers must be designed to handle bulk data gracefully. **What is Bulkification?** Bulkification means writing code that efficiently processes collections of records rather than individual records. Instead of performing SOQL queries or DML operations inside loops, bulkified triggers operate on lists, sets, and maps to minimize resource consumption and avoid governor limits. **Key Best Practices:** 1. **Never use SOQL/DML inside loops:** Queries and DML statements inside for-loops quickly hit governor limits (100 SOQL queries, 150 DML statements per transaction). Instead, collect data first, query once, and perform DML outside the loop. 2. **Use Collections:** Leverage Lists, Sets, and Maps to aggregate records and process them in bulk. For example, collect all related IDs in a Set, perform a single query, and store results in a Map for efficient lookup. 3. **One Trigger Per Object:** Maintain a single trigger per object to control execution order and avoid unpredictable behavior. Use trigger handler classes or frameworks to organize logic. 4. **Separate Logic from Triggers:** Move business logic into handler classes following the separation of concerns principle. This improves testability, maintainability, and reusability. 5. **Use Trigger Context Variables:** Leverage Trigger.new, Trigger.old, Trigger.newMap, and Trigger.oldMap appropriately based on the trigger event (before/after insert/update/delete). 6. **Avoid Recursive Triggers:** Implement static boolean variables or recursion handlers to prevent triggers from firing infinitely. 7. **Write Comprehensive Tests:** Test with bulk data (at least 200 records) to verify the trigger handles bulk operations without exceeding governor limits. By following these practices, developers ensure scalable, efficient, and maintainable automation that performs well within Salesforce's multi-tenant architecture and governor limit constraints.
Trigger Bulkification and Best Practices – Complete Guide for Salesforce Platform Developer 1
Why Trigger Bulkification and Best Practices Matter
Trigger bulkification is one of the most critical concepts for any Salesforce developer and a heavily tested topic on the Salesforce Platform Developer 1 (PD1) exam. Understanding bulkification is essential because Salesforce is a multi-tenant environment — meaning many organizations share the same computing resources. To protect shared resources, Salesforce enforces strict governor limits on the number of SOQL queries, DML statements, CPU time, and heap size your code can consume in a single transaction. If your trigger is not bulkified, it will hit these limits and throw runtime exceptions, especially when processing large data volumes through Data Loader, batch jobs, or integrations.
What Is Trigger Bulkification?
Trigger bulkification is the practice of writing Apex triggers so they can efficiently handle one record or up to 200 records (or more, depending on the context) in a single execution. When a DML operation affects multiple records — for example, an import of 1,000 Account records — Salesforce groups those records into chunks of 200 and fires the trigger once per chunk. A bulkified trigger processes all 200 records in one invocation without placing SOQL queries, DML operations, or other governed calls inside a loop.
In simple terms:
- Non-bulkified trigger: Performs a SOQL query or DML statement inside a for loop, executing it once per record. With 200 records, this means 200 queries or 200 DML statements — easily exceeding the limit of 100 SOQL queries or 150 DML statements per transaction.
- Bulkified trigger: Collects data from all records first, performs a single SOQL query (or as few as possible), processes all records in memory, and then performs a single DML operation outside the loop.
How Trigger Bulkification Works — Step by Step
Let's walk through the principles with concrete patterns:
1. Never Place SOQL Queries Inside Loops
Bad Example (Non-Bulkified):
for (Account acc : Trigger.new) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
// Process contacts
}
This fires one SOQL query per account. With 200 accounts, you get 200 SOQL queries and hit the 100-query governor limit.
Good Example (Bulkified):
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) {
accountIds.add(acc.Id);
}
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
if (!contactsByAccount.containsKey(c.AccountId)) {
contactsByAccount.put(c.AccountId, new List<Contact>());
}
contactsByAccount.get(c.AccountId).add(c);
}
// Now process using the map
This uses a single SOQL query regardless of how many accounts are in the trigger context.
2. Never Place DML Statements Inside Loops
Bad Example:
for (Account acc : Trigger.new) {
Contact c = new Contact(LastName = 'Default', AccountId = acc.Id);
insert c;
}
Good Example:
List<Contact> contactsToInsert = new List<Contact>();
for (Account acc : Trigger.new) {
contactsToInsert.add(new Contact(LastName = 'Default', AccountId = acc.Id));
}
insert contactsToInsert;
Collect all records in a list, then perform a single DML statement outside the loop.
3. Use Collections (Maps, Sets, Lists) Extensively
Collections are the foundation of bulkification. Use:
- Sets to collect unique IDs for filtering SOQL queries (e.g., Set<Id>).
- Maps to associate records with their related data for quick lookups without additional queries (e.g., Map<Id, SObject>).
- Lists to aggregate records for bulk DML operations.
4. Use Maps for Quick Lookups
A common pattern is to query related records into a Map<Id, SObject> so you can look them up in O(1) time:
Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds]);
You can then access any account by ID: accountMap.get(someId);
5. One Trigger Per Object
Salesforce best practice dictates having one trigger per object. Multiple triggers on the same object have no guaranteed order of execution, making debugging difficult and behavior unpredictable. Instead, use a single trigger that delegates to a handler class.
6. Trigger Handler Pattern (Logic-Free Triggers)
Keep triggers free of business logic. The trigger should only call methods on a handler class:
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
AccountTriggerHandler.handleTrigger(Trigger.new, Trigger.oldMap, Trigger.operationType);
}
The handler class contains all the logic, making the code testable, maintainable, and reusable.
7. Avoid Recursion
When a trigger performs DML on the same object (or a related object whose trigger then updates the original object), it can cause recursive trigger invocations. Use a static Boolean variable in a helper class to prevent re-entry:
public class TriggerHelper {
public static Boolean isFirstRun = true;
}
In the trigger:
if (TriggerHelper.isFirstRun) {
TriggerHelper.isFirstRun = false;
// Execute logic
}
8. Use Trigger Context Variables Properly
Understand and use the correct context variables:
- Trigger.new — List of new versions of records (available in insert and update triggers).
- Trigger.old — List of old versions of records (available in update and delete triggers).
- Trigger.newMap — Map of new records by Id (available in before update, after insert, after update).
- Trigger.oldMap — Map of old records by Id (available in update and delete triggers).
- Trigger.isBefore / Trigger.isAfter — Indicates the trigger phase.
- Trigger.isInsert / Trigger.isUpdate / Trigger.isDelete / Trigger.isUndelete — Indicates the DML operation.
Use Trigger.oldMap to compare old and new field values in update triggers to determine if a field actually changed, preventing unnecessary processing.
9. Understand the Order of Execution
Salesforce follows a specific order of execution when a record is saved:
1. System validation rules (required fields, field formats)
2. Before triggers execute
3. Custom validation rules
4. Record is saved to the database (but not committed)
5. After triggers execute
6. Assignment rules, auto-response rules, workflow rules
7. Workflow field updates (if any, triggers re-fire)
8. Process Builder and Flows
9. Escalation rules
10. DML is committed to the database
Understanding this order is critical for knowing when to use before vs. after triggers:
- Use before triggers to modify field values on the same record (no DML needed — just change the field on the Trigger.new record).
- Use after triggers to access system-set field values (like Id or auto-number fields), create or update related records, or enqueue async processes.
10. Avoid Hardcoding IDs
Never hardcode record IDs (e.g., RecordType IDs or Profile IDs) in your trigger code. Instead, query for them dynamically or use Custom Metadata Types, Custom Labels, or Schema describe methods.
Summary of Key Best Practices:
- Bulkify all trigger code — never use SOQL or DML inside loops.
- Use collections (Set, Map, List) to process data efficiently.
- One trigger per object.
- Use the Trigger Handler pattern — keep triggers logic-free.
- Prevent recursion with static variables.
- Use before triggers for same-record field updates; use after triggers for related-record operations.
- Always consider the order of execution.
- Write comprehensive test classes with bulk data (at least 200 records) to verify bulkification.
- Avoid hardcoding IDs.
Exam Tips: Answering Questions on Trigger Bulkification and Best Practices
Tip 1: Look for SOQL or DML Inside Loops
The most common exam question pattern shows you a code snippet and asks which option is bulkified correctly. Immediately scan for SOQL queries ([SELECT ...]) or DML statements (insert, update, delete) inside for loops. If you see them inside a loop, that answer is wrong.
Tip 2: Identify the Correct Collection Pattern
Exam questions often present multiple options for achieving the same result. The correct answer almost always involves collecting IDs into a Set, querying once with a WHERE ... IN :setVariable clause, storing results in a Map, and then processing inside the loop using Map.get().
Tip 3: Know When to Use Before vs. After Triggers
If the question involves updating a field on the same record that fired the trigger, the answer is a before trigger (and you do NOT need a DML statement — just set the field on the Trigger.new record). If the question involves creating or updating related records, the answer is an after trigger with explicit DML.
Tip 4: Watch for Recursion Scenarios
If a question describes unexpected behavior like infinite loops or hitting governor limits on an update trigger that also performs an update, think about recursion prevention with a static Boolean variable.
Tip 5: Remember the One Trigger Per Object Rule
If the exam asks about best practices for organizing trigger code, the answer involves having one trigger per object delegating to a handler class — not multiple triggers on the same object.
Tip 6: Understand Trigger.new vs. Trigger.old
Questions may test whether you know that Trigger.new records are read-only in after triggers but can be modified in before triggers. Also, Trigger.old is not available in insert triggers because there is no old version of the record.
Tip 7: Pay Attention to Governor Limit Numbers
Key limits to remember: 100 SOQL queries per synchronous transaction, 150 DML statements per transaction, 200 records per trigger chunk, 10,000 records retrieved per SOQL query, and 50,000 total records retrieved by SOQL queries per transaction.
Tip 8: Test Classes Must Use Bulk Data
If a question asks about testing best practices, the correct answer always involves creating at least 200 records to test that the trigger handles bulk operations without hitting governor limits. Tests should use @isTest annotation and Test.startTest() / Test.stopTest() to get a fresh set of governor limits.
Tip 9: Eliminate Obviously Wrong Answers First
On multi-choice questions, quickly eliminate answers that contain SOQL inside loops, DML inside loops, or hardcoded IDs. This often reduces your choices to two options, making it easier to identify the correct one.
Tip 10: Scenario-Based Questions
Many PD1 questions present a business scenario (e.g., 'When an Opportunity is closed-won, create an invoice record'). Think through: Which trigger event? (after update). How to detect the change? (Compare Trigger.new and Trigger.oldMap values). How to bulkify? (Collect Opportunity IDs, query related data in one query, create Invoice records in a list, perform one insert). The answer that follows this pattern is correct.
🎓 Unlock Premium Access
Salesforce Certified Platform Developer I + ALL Certifications
- 🎓 Access to ALL Certifications: Study for any certification on our platform with one subscription
- 2750 Superior-grade Salesforce Certified Platform Developer I practice questions
- Unlimited practice tests across all certifications
- Detailed explanations for every question
- PD1: 5 full exams plus all other certification exams
- 100% Satisfaction Guaranteed: Full refund if unsatisfied
- Risk-Free: 7-day free trial with all premium features!