Bulk-safe code is the difference between an Apex class that works in unit tests and one that survives 200-record DML batches.
Pattern 1: Single SOQL outside the loop.
`apex // BAD for (Account a : Trigger.new) { Contact[] cts = [SELECT Id FROM Contact WHERE AccountId = :a.Id]; // SOQL in loop }
// GOOD Set<Id> accIds = new Set<Id>(); for (Account a : Trigger.new) accIds.add(a.Id); Map<Id, List<Contact>> contactsByAcc = new Map<Id, List<Contact>>(); for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds]) { if (!contactsByAcc.containsKey(c.AccountId)) contactsByAcc.put(c.AccountId, new List<Contact>()); contactsByAcc.get(c.AccountId).add(c); } `
Pattern 2: Single DML at the end.
apex List<Contact> toUpdate = new List<Contact>(); for (Account a : Trigger.new) { for (Contact c : contactsByAcc.get(a.Id) ?? new List<Contact>()) { c.Phone = a.Phone; toUpdate.add(c); } } if (!toUpdate.isEmpty()) update toUpdate;
Pattern 3: Map for O(1) lookups.
apex Map<Id, Account> oldMap = (Map<Id, Account>) Trigger.oldMap; for (Account a : Trigger.new) { if (a.Phone != oldMap.get(a.Id).Phone) { // changed } }
Pattern 4: Set for membership.
apex Set<String> validStatuses = new Set<String>{'Active', 'Pending'}; for (Account a : Trigger.new) { if (validStatuses.contains(a.Status__c)) { ... } }
Pattern 5: Aggregate when summing. Use GROUP BY instead of looping to sum amounts.
Pattern 6: Bulk-safe DML. Use Database.insert(list, false) for partial-success when bulk inserting potentially-bad records.
Pattern 7: Don't pass single records. Methods accept and return Lists, not individuals. Forces bulk-thinking.
Pattern 8: Static caching. Within a transaction, cache expensive describe/SOQL results in static Maps.
Anti-patterns:
- SOQL/DML inside loops.
list.contains()inside loops (O(n²)).- Querying related records one at a time.
- Single-record signatures forcing callers to loop.
The bulk discipline is what separates Apex code from script-style code.
