Intermediate 35 min read

Bulkification Patterns

By the end of this lesson, you'll be able to:

  • Apply the "collect, then act once" pattern to fix both SOQL-in-a-loop and DML-in-a-loop
  • Recognize bulkification as a single, unified fix for both problems from this module
  • Rewrite an unbulkified method to be safe at any data volume

Prerequisites: "The DML-in-a-Loop Problem"

The one fix for both problems

// Fixes SOQL-in-a-loop
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : opportunities) {
    accountIds.add(opp.AccountId);
}
Map<Id, Account> accountsById = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);

// Fixes DML-in-a-loop
List<Account> accountsToUpdate = new List<Account>();
for (Account acc : accounts) {
    acc.Industry = 'Technology';
    accountsToUpdate.add(acc);
}
update accountsToUpdate;
  1. // Fixes SOQL-in-a-loop — the label for the first fix.
  2. Set<Id> accountIds = new Set<Id>(); — collects, rather than queries, inside the loop.
  3. for (Opportunity opp : opportunities) { — loops over the opportunities.
  4. accountIds.add(opp.AccountId); — collects the id, no query yet.
  5. } — closes the loop.
  6. Map<Id, Account> accountsById = new Map<Id, Account>( — one query, outside the loop.
  7. [SELECT Id, Name FROM Account WHERE Id IN :accountIds] — fetches every needed Account at once.
  8. ); — closes the constructor call.
  9. (blank line)
  10. // Fixes DML-in-a-loop — the label for the second fix.
  11. List<Account> accountsToUpdate = new List<Account>(); — collects, rather than updates, inside the loop.
  12. for (Account acc : accounts) { — loops over the accounts.
  13. acc.Industry = 'Technology'; — changes a field in memory.
  14. accountsToUpdate.add(acc); — collects it, no DML yet.
  15. } — closes the loop.
  16. update accountsToUpdate; — one DML statement, outside the loop.

Both fixes share the exact same shape: collect inside the loop, act once outside it — a Set/Map for reads (Module 16), a List for writes. This one pattern, applied consistently, is what "bulk-safe code" actually means in practice.

Rewriting an unbulkified method

// BEFORE: fails at scale
public void flagLargeOpportunities(List<Opportunity> opportunities) {
    for (Opportunity opp : opportunities) {
        if (opp.Amount > 100000) {
            opp.Priority__c = 'High';
            update opp; // DML in a loop
        }
    }
}

// AFTER: bulk-safe at any volume
public void flagLargeOpportunities(List<Opportunity> opportunities) {
    List<Opportunity> toUpdate = new List<Opportunity>();
    for (Opportunity opp : opportunities) {
        if (opp.Amount > 100000) {
            opp.Priority__c = 'High';
            toUpdate.add(opp);
        }
    }
    update toUpdate;
}
  1. // BEFORE: fails at scale — the label for the flawed version.
  2. public void flagLargeOpportunities(List<Opportunity> opportunities) { — the method.
  3. for (Opportunity opp : opportunities) { — loops over the opportunities.
  4. if (opp.Amount > 100000) { — the business rule.
  5. opp.Priority__c = 'High'; — applies it.
  6. update opp; — DML in a loop, once per matching record.
  7. } — closes the if.
  8. } — closes the loop.
  9. } — closes the method.
  10. (blank line)
  11. // AFTER: bulk-safe at any volume — the label for the fixed version.
  12. public void flagLargeOpportunities(List<Opportunity> opportunities) { — the same method signature.
  13. List<Opportunity> toUpdate = new List<Opportunity>(); — collects instead of updating immediately.
  14. for (Opportunity opp : opportunities) { — the same loop.
  15. if (opp.Amount > 100000) { — the same rule.
  16. opp.Priority__c = 'High'; — the same effect.
  17. toUpdate.add(opp); — collected, not yet saved.
  18. } — closes the if.
  19. } — closes the loop.
  20. update toUpdate; — one update statement, outside the loop.
  21. } — closes the method.

Same logic, same result — but the "after" version runs exactly one update statement regardless of whether opportunities has 5 records or 5,000, satisfying this module's core habit directly.

Exercise

Rewrite this method to be bulk-safe: collect matching Contacts into a list and update once, instead of updating inside the loop.

Show hint

Move update contact; outside the loop, collecting into a List<Contact> instead.

APEX

Bulkification Patterns Quiz

1. What is the "collect, then act once" pattern?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

Governor limits exist because Apex runs on shared, multi-tenant infrastructure. The single most important habit for staying within them is bulkifying your code: query and write in Lists, never inside a loop.