100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Bulkification and Best Practices

How to write Apex trigger logic that stays safely within governor limits regardless of whether it processes one record or two hundred.

Triggers & LogicIntermediate8 min readJul 10, 2026
Analogies

Why Bulkification Matters

Salesforce enforces per-transaction governor limits, including a maximum of 100 SOQL queries and 150 DML statements, precisely because a single trigger invocation can process up to 200 records at once when triggered by a bulk API load, Data Loader import, or bulk API operation. Bulkification means writing trigger and handler logic so that all SOQL queries and DML statements execute once per transaction, operating on entire collections of records, rather than once per record inside a loop, which is what actually causes those limits to be exceeded.

🏏

Cricket analogy: Bulkification is like a groundstaff crew rolling the entire pitch in one continuous pass before a Test match rather than walking out to flatten one blade of grass at a time between every single delivery.

SOQL and DML Inside Loops

Placing a SOQL query or DML statement, such as an update, inside a for loop that iterates over Trigger.new is the single most common cause of hitting governor limits, because a bulk load of 200 records would trigger 200 separate queries or DML statements in that one transaction, quickly exceeding the 100-query or 150-DML ceiling and throwing a System.LimitException. The fix is to move any needed query outside the loop, gather the relevant Ids first using a Set<Id>, run one bulk query, then loop over the in-memory results to build a single bulk DML list executed once after the loop.

🏏

Cricket analogy: Running a SOQL query inside a loop over 200 records is like sending a runner to check the scoreboard individually after every single ball of a 200-ball innings instead of just glancing at the board once when needed.

apex
// Anti-pattern: SOQL and DML inside a loop
for (Opportunity opp : Trigger.new) {
    Account acc = [SELECT Id, Rating FROM Account WHERE Id = :opp.AccountId]; // query per record
    acc.Rating = 'Hot';
    update acc; // DML per record
}

// Bulk-safe pattern
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : Trigger.new) {
    accountIds.add(opp.AccountId);
}
Map<Id, Account> accountsById = new Map<Id, Account>(
    [SELECT Id, Rating FROM Account WHERE Id IN :accountIds]
);
List<Account> accountsToUpdate = new List<Account>();
for (Opportunity opp : Trigger.new) {
    Account acc = accountsById.get(opp.AccountId);
    if (acc != null) {
        acc.Rating = 'Hot';
        accountsToUpdate.add(acc);
    }
}
if (!accountsToUpdate.isEmpty()) {
    update accountsToUpdate;
}

A trigger that works fine when tested with a single record in the UI can fail catastrophically in production the first time someone imports 200 records via Data Loader, because the SOQL-in-loop pattern that silently worked at scale of one now issues 200 queries and throws a 'Too many SOQL queries' limit exception.

Collections and Maps for Bulk-Safe Logic

A common bulk-safe pattern is to first collect related Ids into a Set<Id> while looping over Trigger.new, then issue a single SOQL query using an IN clause against that set, and finally store the results in a Map<Id, sObject> or Map<Id, List<sObject>> for O(1) lookups when building the final DML list. This pattern, summarized as 'collect, query once, map, then act', scales identically whether the trigger fires on one record or two hundred, because the number of queries and DML statements stays constant regardless of the record count.

🏏

Cricket analogy: Collecting Ids into a set before one bulk query is like a scorer noting down every batsman due to bat before checking the entire lineup card once, rather than flipping through the lineup card separately for each individual batsman.

Bulk-Safe Trigger Design Checklist

Beyond avoiding SOQL and DML in loops, a bulk-safe trigger keeps the trigger body itself minimal, delegating to a handler class; uses a static Boolean or Set<Id> as a recursion guard to prevent the same records from being reprocessed if the handler's own DML re-fires the trigger; and always assumes Trigger.new could contain anywhere from 1 to 200 records rather than hardcoding logic for a single record. Testing with bulk data, typically 200+ records, in unit tests is the only reliable way to catch a bulkification bug before it reaches production.

🏏

Cricket analogy: A recursion guard preventing a trigger from reprocessing its own DML is like a third umpire's protocol that a review, once conclusively decided, cannot be re-reviewed within the same passage of play, preventing an endless review loop.

A simple static recursion guard: public class TriggerControl { public static Boolean accountTriggerHasRun = false; } and the handler checks if (TriggerControl.accountTriggerHasRun) return; before setting it true, preventing infinite loops when the handler's own after-update DML re-fires AccountTrigger.

  • Bulkification means SOQL queries and DML statements run once per transaction on full collections, not once per record inside a loop.
  • A single trigger invocation can process up to 200 records at once from bulk API loads or Data Loader imports.
  • The 'collect Ids, query once, map, then act' pattern keeps query and DML counts constant regardless of record volume.
  • Never place a SOQL query or DML statement inside a loop over Trigger.new - this is the top cause of governor-limit exceptions.
  • Use a static recursion guard to stop a handler's own DML from re-triggering itself and causing infinite loops.
  • Always write and test trigger logic assuming Trigger.new can contain 1 to 200 records, not just one.
  • Unit tests should insert 200+ records via Test.startTest()/stopTest() to catch bulkification bugs before production.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#BulkificationAndBestPractices#Bulkification#Matters#SOQL#DML#StudyNotes#SkillVeris#ExamPrep