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

Governor Limits

Understand why Salesforce enforces per-transaction governor limits and how to write bulk-safe Apex that never hits SOQL, DML, CPU, or heap ceilings.

Practical ApexIntermediate10 min readJul 10, 2026
Analogies

Why Governor Limits Exist

Apex runs on a multi-tenant platform where thousands of organizations share the same underlying infrastructure, so Salesforce enforces strict per-transaction governor limits to guarantee no single runaway process can monopolize shared resources and degrade performance for every other customer. These limits cap things like the number of SOQL queries (100 synchronous, 200 asynchronous), DML statements (150), records processed by DML (10,000), CPU time (10,000ms synchronous), and heap size (6MB synchronous) within a single transaction. Unlike a memory leak on a dedicated server that only hurts you, exceeding a limit in Apex throws an uncatchable LimitException that aborts the entire transaction — there is no graceful degradation.

🏏

Cricket analogy: It's like the fielding restrictions during Powerplay overs in an ODI — every team gets the same rule regardless of how good their batting lineup is, ensuring no single team's aggression breaks the format for everyone else.

The Bulkification Pattern

The single most important discipline for staying inside governor limits is bulkification: never place a SOQL query or DML statement inside a for-loop. Because triggers can fire on batches of up to 200 records simultaneously (from a Data Loader import, an API bulk call, or a bulk UI action), any query or DML placed inside a loop body executes once per record, and 200 iterations will quickly exceed the 100-synchronous-SOQL-query limit or 150-DML-statement limit. The correct pattern is to collect the relevant Ids or data into a Set or List first, issue a single SOQL query or DML statement outside the loop using bulk collections, and use a Map keyed by Id to look up related data inside the loop without re-querying.

🏏

Cricket analogy: It's like a captain making individual bowling changes after every single ball instead of planning a full over's field placement in advance — bulkifying means setting the field once per over (loop) rather than re-arranging fielders 6 times.

apex
// BAD: SOQL and DML inside a loop — fails at scale
trigger AccountTrigger on Account (before update) {
    for (Account acc : Trigger.new) {
        List<Contact> cons = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // SOQL in loop
        for (Contact c : cons) {
            c.Status__c = 'Active';
            update c; // DML in loop
        }
    }
}

// GOOD: bulkified — one query, one DML statement regardless of batch size
trigger AccountTrigger on Account (before update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        accountIds.add(acc.Id);
    }

    List<Contact> consToUpdate = [
        SELECT Id, Status__c FROM Contact WHERE AccountId IN :accountIds
    ];
    for (Contact c : consToUpdate) {
        c.Status__c = 'Active';
    }
    update consToUpdate; // single bulk DML call
}

Common Limits and How They're Consumed

Beyond SOQL and DML counts, three other limits catch developers off guard. CPU time (10,000ms for synchronous transactions, 60,000ms for asynchronous) accumulates across the whole transaction, including time spent in formula field evaluation and validation rules, not just your explicit code — a heavy nested loop doing string manipulation on 10,000 records can burn through it even with zero queries. Heap size (6MB synchronous, 12MB asynchronous) is consumed by variables held in memory, so loading a massive List<Account> with dozens of fields for 50,000 records can exceed it even though no single record is large. Future methods and Queueable Apex exist partly to escape synchronous limits, but they introduce their own constraint: only one Queueable can be chained from another, and future methods cannot be called from other future methods.

🏏

Cricket analogy: CPU time accumulating silently is like a bowler's over-rate penalty accruing across the whole innings from slow field changes, drinks breaks, and DRS reviews, not just the actual ball-bowling time — it all counts against the clock.

Use Limits.getQueries(), Limits.getDmlStatements(), and Limits.getCpuTime() (paired with their getLimitXxx() counterparts) during development to log how close a transaction is to a ceiling. This is especially useful in batch Apex where record volumes are unpredictable.

Escaping Synchronous Limits with Async Apex

When a genuinely large workload cannot fit inside synchronous limits — for example, recalculating a rollup across 500,000 child records — Batch Apex is the standard escape valve. It implements Database.Batchable<sObject>, splitting the total record set into chunks (default 200, configurable down to 1) and running each chunk as its own transaction with its own fresh set of governor limits, so a job that would blow the 10,000-record DML limit in one shot can process millions of records safely over many small transactions. Queueable Apex is preferred over the older @future annotation for one-off async work because it accepts complex object parameters (not just primitives), returns a monitorable Id, and supports chaining one job into the next for sequential multi-step processing.

🏏

Cricket analogy: Batch Apex splitting 500,000 records into 200-record chunks is like a Test match being played across 5 separate days rather than trying to fit it into one impossible session — each day (batch) gets its own fresh overs quota.

Governor limits are per-transaction, not per-class or per-method — every trigger, class, and flow invoked within the same execution context shares the same limit pool. A trigger that looks perfectly bulk-safe on its own can still blow the SOQL limit if it fires alongside another trigger or a Process Builder/Flow on the same object update, because their query counts stack within that one transaction.

  • Governor limits exist because Apex runs on shared, multi-tenant infrastructure and must prevent any one org from starving others.
  • Never place SOQL queries or DML statements inside a for-loop — always bulkify using collections and Maps.
  • Key synchronous limits: 100 SOQL queries, 150 DML statements, 10,000 DML rows, 10,000ms CPU time, 6MB heap.
  • CPU time and heap accumulate across the entire transaction, including formula fields and validation rules, not just your explicit code.
  • Batch Apex splits large record sets into independent chunked transactions, each with its own fresh governor limits.
  • Queueable Apex is generally preferred over @future for its ability to accept complex parameters and support job chaining.
  • Limits are shared per-transaction across all triggers, flows, and classes invoked together, not scoped to a single class.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#GovernorLimits#Governor#Limits#Exist#Bulkification#StudyNotes#SkillVeris#ExamPrep