Apex Bulkification: The Only Pattern You Need to Memorize
Every Apex developer learns "don't put SOQL in a loop" on day one. But bulkification goes deeper than that. The real pattern is: collect, query, map, process, DML. Every trigger handler, every batch class, every queueable should follow this exact sequence. Once you internalize it, you will never hit a governor limit again.
Collect means gathering the IDs or field values you need from Trigger.new. Query means doing a single SOQL query using an IN clause with those collected values. Map means putting the query results into a Map<Id, SObject> for O(1) lookups. Process means looping through Trigger.new and using the map to enrich each record. DML means collecting all records to insert/update into a list and doing one DML operation at the end.
This pattern scales from 1 record to 10,000. The number of SOQL queries and DML statements is always constant regardless of batch size. I have used this exact pattern in every trigger handler at Mobilization Funding and Cloud Nimbus. It is boring. It is repetitive. It works every single time.
Code Example
// The Collect-Query-Map-Process-DML pattern:
public class OpportunityTriggerHandler {
public static void afterUpdate(
List<Opportunity> newList,
Map<Id, Opportunity> oldMap
) {
// 1. COLLECT — gather what you need
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : newList) {
if (opp.StageName != oldMap.get(opp.Id).StageName) {
accountIds.add(opp.AccountId);
}
}
if (accountIds.isEmpty()) return; // exit early
// 2. QUERY — one SOQL, not N
Map<Id, Account> accountMap = new Map<Id, Account>(
[SELECT Id, Last_Opp_Stage_Change__c
FROM Account WHERE Id IN :accountIds]
);
// 3. MAP — already done (Map<Id, Account>)
// 4. PROCESS — enrich using the map
List<Account> toUpdate = new List<Account>();
for (Opportunity opp : newList) {
if (opp.StageName != oldMap.get(opp.Id).StageName) {
Account acc = accountMap.get(opp.AccountId);
if (acc != null) {
acc.Last_Opp_Stage_Change__c = System.now();
toUpdate.add(acc);
}
}
}
// 5. DML — one operation, not N
if (!toUpdate.isEmpty()) {
update toUpdate;
}
}
}Need this implemented in your org?
I've shipped these patterns in production for 10+ years.
View Consulting →Enjoyed this? Get more like it.
Glen's Musings — AI, investing, and building things. Occasional. Free.
More Apex Tips
The Test Data Factory Pattern That Saves Hours Every Sprint
Most Salesforce projects start with test classes that create their own data inline. By sprint three, you have 40 test cl...
Read moreAdvancedQueueable vs Future vs Batch: When to Use Each Async Pattern
Salesforce gives you three async patterns and choosing the wrong one wastes hours. Here is the decision tree I use on ev...
Read moreIntermediateCustom Metadata Types: Runtime Configuration Without Deployments
Custom Settings were the old way to store configuration. Custom Metadata Types are the upgrade, and the difference matte...
Read more