How can you bulkify a trigger that currently fails when processing more than 200 records?
•31 min read

Why the Trigger Fails with More Than 200 Records
A trigger often fails during bulk operations (like Data Loader, mass update, API integrations, Process Builder, Flow, etc.) because it is written assuming only one record at a time, which causes:
- SOQL queries inside loops
- DML statements inside loops
- Not using Sets/Maps
- Overwriting instead of grouping logic per record
Salesforce governor limits restrict:
- Max 100 SOQL queries per transaction
- Max 150 DML statements per transaction
- Max 10,000 records returned from SOQL
When loops perform SOQL or DML repeatedly, these limits are hit — causing the trigger to fail when processing more than 200 records.
🔹 Bulkification Strategy — Key Points
| Bad practice | Good practice |
|---|---|
| Query inside loop | Query once outside the loop |
| DML inside loop | Collect all records and do 1 DML |
| Assume 1 record | Handle Trigger.new list |
| Hard-code Id | Use Set of Ids |
| No Maps | Use Maps for quick lookup |
🔹 Example of a Non-Bulkified Trigger (BAD ❌)
trigger UpdateAccountCount on Contact (after insert) {
for(Contact con : Trigger.new){
Account acc = [SELECT Id, Contact_Count__c FROM Account WHERE Id = :con.AccountId];
acc.Contact_Count__c = acc.Contact_Count__c + 1;
update acc;
}
}
JavaScript❌ Problems:
- SOQL inside loop
- DML inside loop
- Runs fine for 1 record but fails for bulk loads
🔹 Correct Bulkified Trigger (GOOD ✔)
trigger UpdateAccountCount on Contact (after insert) {
Set<Id> accountIds = new Set<Id>();
for(Contact con : Trigger.new){
if(con.AccountId != null){
accountIds.add(con.AccountId);
}
}
Map<Id, Account> accMap = new Map<Id, Account>(
[SELECT Id, Contact_Count__c FROM Account WHERE Id IN :accountIds]
);
for(Contact con : Trigger.new){
if(con.AccountId != null && accMap.containsKey(con.AccountId)){
accMap.get(con.AccountId).Contact_Count__c += 1;
}
}
update accMap.values(); // ONE DML
}
JavaScript✔ Benefits:
- Single SOQL query
- Single DML update
- Works for 1 record and 10,000+ related contacts
- Fully governor-limit compliant
🔹 Additional Best Practices
1️⃣ Use Trigger Context Variables
if(Trigger.isInsert && Trigger.isAfter) { ... }
JavaScript2️⃣ Never Use Future or @InvocableMethod for Bulk Fix Unless Required
Always try to bulkify logic inside the trigger instead of shifting DML to asynchronous mode unnecessarily.
3️⃣ Use Helper Class for Logic (Trigger Handler Pattern)
trigger UpdateAccountCount on Contact (after insert) {
ContactTriggerHandler.updateAccountCount(Trigger.new);
}
JavaScriptpublic class ContactTriggerHandler {
public static void updateAccountCount(List<Contact> newList) {
Set<Id> accIds = new Set<Id>();
for(Contact c : newList){
if(c.AccountId != null){
accIds.add(c.AccountId);
}
}
Map<Id, Account> accMap = new Map<Id, Account>(
[SELECT Id, Contact_Count__c FROM Account WHERE Id IN :accIds]
);
for(Contact c : newList){
if(c.AccountId != null && accMap.containsKey(c.AccountId)){
accMap.get(c.AccountId).Contact_Count__c++;
}
}
update accMap.values();
}
}
JavaScript🔹 Common Errors to Watch For
| Error | Fix |
|---|---|
Too many SOQL queries: 101 | Remove SOQL from loops |
Too many DML statements: 151 | Remove DML from loops |
NullPointerException | Check for null with if(object != null) |
Mixed DML error | Move user-role operations to future or queueable |
| Infinite trigger recursion | Use static variable check |
🔹 Bulk Testing Tip (Apex Test Class)
@isTest
private class UpdateAccountCountTest {
@isTest static void testBulkContacts() {
Account acc = new Account(Name = 'Test Acc', Contact_Count__c = 0);
insert acc;
List<Contact> contacts = new List<Contact>();
for(Integer i = 0; i < 300; i++){
contacts.add(new Contact(LastName = 'Test', AccountId = acc.Id));
}
insert contacts;
Account updated = [SELECT Contact_Count__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(300, updated.Contact_Count__c);
}
}
JavaScript💡 Final Summary
To bulkify a trigger:
✔ Process all records from Trigger.new
✔ Collect required IDs into Set
✔ Query once using IN operator
✔ Use Maps for lookup
✔ Perform 1 DML outside loops
When done correctly, the trigger:
- Never hits governor limits
- Works efficiently for API bulk operations
- Is scalable and production-safe
Related Posts

How to Automatically create a follow-up Task when a Lead is converted

How You need to update a related child record whenever a parent record’s status changes, but only if the status is “Closed Won.” How would you design this in Apex?
