← Back to Architecture
ARCH-002 Architecture 20 min read For: All Salesforce Tech Leaders

Governor Limits

VS

Vishal Sharma

Salesforce Architecture Specialist · Updated May 2026

What you will learn in this tutorial
  • What Governor Limits are and why they exist in a multi-tenant platform
  • The six primary categories of limits and which ones your project will most likely hit
  • How Governor Limits behave in triggers, flows, and async contexts — and why the behaviour differs
  • The "bulkification" architecture pattern and why it is the single most important Salesforce design principle
  • How to surface Governor Limit risk early in a programme, before it becomes a delivery crisis

The Tax Nobody Puts in the Budget

Every Salesforce programme has a moment where someone says the words: "It was working fine in sandbox." The investigation begins. The developer comes back with a phrase that will mean nothing to most people in the room: "We hit a Governor Limit."

The Delivery Manager doesn't know what to do with this information. The business stakeholder hears "a technical problem." The CTO hears "an unexpected cost." All three reactions are understandable — and all three are preventable if Governor Limits are understood and budgeted for at the architecture stage, not discovered in UAT.

This tutorial is for the people in that room who want to understand Governor Limits well enough to ask the right questions, make the right decisions, and not be blindsided when the project hits them. You don't need to be able to fix them yourself. You need to know they're coming.

🔑
The Essential Mental Model

Governor Limits are not bugs, and they are not signs of poor Salesforce engineering. They are the enforcement mechanism of multi-tenancy — the rule that says no single customer can consume so many shared resources that everyone else suffers. Understanding this changes how you approach every architecture decision on the platform.

Why Governor Limits Exist

Salesforce's infrastructure is shared. Thousands of organisations run their automation, their queries, their data writes on the same underlying servers at the same time. Without resource governance, a single poorly-written trigger could bring the platform to its knees for every other tenant.

Governor Limits solve this by imposing hard caps on how much of Salesforce's shared resources any single transaction can consume. Exceed the cap and the transaction fails — not degrades, not slows, fails. This is a deliberate design choice. A fast failure with a clear error is infinitely better than a slow degradation that affects 150,000 other customers undetected.

The important word in the previous paragraph is "transaction." Governor Limits apply per-transaction, not per-record. This distinction is the source of most Governor Limit problems in Salesforce programmes — and the source of the most important Salesforce architecture pattern: bulkification.

The Six Primary Categories

Salesforce documents dozens of specific limits, but leaders need to understand six categories. These are the ones your programme will encounter.

1. Apex CPU Time (10,000ms synchronous / 60,000ms asynchronous)

Every line of Apex code that executes consumes CPU time. The limit is 10,000 milliseconds — 10 seconds — for a synchronous transaction. This sounds generous until you have a trigger calling a helper method that calls a utility class that calls a calculation engine, and all of it runs 200 times because the batch size is 200 records.

CPU limits are the hardest to diagnose because they're cumulative across the entire call stack. A trigger that runs in 200ms per record becomes a 40,000ms problem with a 200-record batch — four times the limit.

2. Heap Size (6MB synchronous / 12MB asynchronous)

Heap size governs how much data can be held in memory during a transaction. Programmes that retrieve large result sets, build complex in-memory data structures, or deserialise large JSON payloads from external APIs will encounter this. 6MB sounds generous; a SOQL query returning 50,000 Account records with 40 fields each does not sound generous.

3. SOQL Queries (100 synchronous / 200 asynchronous)

This is the limit most projects hit first. A trigger on Opportunity is fired. It queries related OpportunityLineItems. It calls a helper that queries PricebookEntries. The helper calls a validation utility that queries a Custom Metadata Type. You're at 4 queries for one record. Scale to 200 records with a batch and careless code still only makes 4 queries (if written correctly). Written incorrectly — one query per record — you hit the limit at 100 records.

4. DML Statements (150 synchronous)

Every insert, update, delete, or upsert in your transaction counts against the DML limit. The same pattern applies: one DML per record across a batch of 200 records fails at the 150 mark. One DML per batch of any size uses one statement.

5. Records Retrieved by SOQL (50,000 rows)

Each SOQL query can return at most 50,000 rows. Across all queries in a transaction, the total rows retrieved from the database cannot exceed 50,000. This is a different limit from the query count — you can have 100 queries that each return 500 rows, and you'll be well within both limits. One query returning 55,000 rows fails regardless of how many queries you have left.

6. Async Method Calls and Callouts

Future method calls (50 per transaction), Queueable enqueues (50 per transaction), and external callouts (100 per transaction, max 120 seconds timeout) are governed separately. These limits create architectural constraints when you're trying to "escape" synchronous limits by moving work to async — you can't enqueue an unlimited number of async jobs from a single transaction.

⚠️
The Limit Your Partner Didn't Mention

The 150 DML statements limit applies to the entire transaction — including DML triggered by automation that your code calls. If your trigger inserts a record, and that insert triggers a Flow that updates another record, and that update triggers another trigger — all of those DML statements count against the same 150 limit. This cascading effect is where most "works in isolation, fails in production" problems come from.

The Bulkification Principle

Bulkification is the architectural pattern that prevents the majority of Governor Limit violations. It deserves its own section because it is not an optimisation — it is the fundamental design principle of Salesforce development. Everything else is secondary.

The principle is simple: never put a SOQL query or DML statement inside a loop. Collect all the data you need first, process it in memory, then write back in one operation.

// NON-BULKIFIED — will fail at 100 records
trigger OpportunityTrigger on Opportunity (before insert) {
    for (Opportunity opp : Trigger.new) {
        // This runs once per Opportunity — 1 query per record
        List<Account> accs = [SELECT Id, Industry
                               FROM   Account
                               WHERE  Id = :opp.AccountId]; // ❌
        opp.Industry__c = accs[0].Industry;
    }
}

// BULKIFIED — handles any batch size
trigger OpportunityTrigger on Opportunity (before insert) {
    Set<Id> accountIds = new Set<Id>();
    for (Opportunity opp : Trigger.new) {
        accountIds.add(opp.AccountId);
    }
    // One query for all records in the batch
    Map<Id, Account> accountMap = new Map<Id, Account>(
        [SELECT Id, Industry FROM Account WHERE Id IN :accountIds] // ✅
    );
    for (Opportunity opp : Trigger.new) {
        opp.Industry__c = accountMap.get(opp.AccountId)?.Industry;
    }
}

The difference is not a micro-optimisation. The non-bulkified version fails at 100 records. The bulkified version handles 200 records — the maximum batch size for triggers — with one query. The non-bulkified version will pass unit tests (which typically run with 1-5 records) and fail in production (where data loads happen in batches of 200).

What to Ask Your Architect

In any technical review, ask: "Are all triggers and flows bulkified?" The follow-up: "How was that verified?" The answer should be "automated tests that explicitly test 200-record batches" — not "we checked manually." If it's not tested at scale, it's not confirmed as bulkified.

Limits in Flows vs Apex

Flows have their own Governor Limits, which are different from Apex limits — but they share the same transaction context. This is one of the most important things leaders need to understand as Salesforce pushes Flows as the primary automation tool.

A record-triggered Flow that runs in the same transaction as a trigger does not get its own separate limit allocation. It consumes from the same 100-query, 150-DML, 10,000ms budget that the trigger is using. Adding a Flow to a heavily automated object can push a transaction that was within limits over the edge.

Flows also have specific limits around loop iterations (2,000 max) and SOQL queries within loops — which makes the same bulkification principle apply to Flow design as to Apex. The difference is that Flow developers are often non-technical, making it easier for anti-patterns to slip through without code review.

💡
The Compound Limit Problem

The most dangerous Governor Limit scenario is not a single bad trigger or a single bad Flow. It's a heavily automated object — Opportunity, Case, Account — where multiple triggers, multiple Flows, and multiple validation rules all share the same transaction limit budget. The individual pieces passed testing. The combination fails in production with a real data load. This is the scenario your architecture review should be explicitly checking for.

Async as the Escape Valve

When synchronous transaction limits are genuinely insufficient for the required business logic, the architectural solution is to move work to asynchronous processing. Salesforce provides several async mechanisms, each with its own limits and tradeoffs.

Future Methods — the simplest form of async. Runs in a separate transaction with higher limits (60,000ms CPU, 200 SOQL queries). Limitation: cannot be called from another async context, and cannot pass sObjects directly (only primitives and collections of primitives).

Queueable Apex — more flexible than Future. Can be chained, can accept sObject parameters. The recommended pattern for most complex async scenarios. Up to 50 can be enqueued per transaction.

Batch Apex — for processing large data volumes (up to 50 million records) in chunks. Each chunk (typically 200 records) runs in its own transaction with full limit allocations. The overhead is scheduling latency — Batch jobs queue behind other jobs in the platform's batch scheduler.

The decision to use async is a design decision, not a tactical fix. If you're adding async to fix a Governor Limit error in a live production system, the underlying architecture problem still exists — you've just deferred it. The correct approach is to design for async from the architecture phase when the business logic requires it.

Governor Limits on Your Risk Register

For any Salesforce programme, Governor Limit risk should be explicitly tracked and managed. Here's what that looks like in practice.

At architecture stage: For each major automation-heavy object (typically Opportunity, Case, Account, Lead), the architecture document should include an explicit assessment of anticipated automation complexity and limit exposure. If the object is already in production with existing triggers, the current limit consumption should be profiled before adding new automation.

At build stage: Code review should include a checklist item specifically for bulkification. Automated tests should include explicit bulk test scenarios (200 records). Integration tests should test the full automation stack, not individual triggers in isolation.

At UAT stage: Data volume for UAT tests should reflect realistic production volumes, not the 10-20 records that most UAT scenarios use. A trigger tested with 20 records that processes 200 in production will behave differently.

Post go-live: Salesforce's Apex profiling tools (Developer Console, Apex Debug Log with profiling enabled) should be used to monitor limit consumption in the first weeks after go-live. Catching a 90% limit consumption problem before it becomes a failure is significantly cheaper than a production incident.

Key Takeaways

  • Governor Limits are hard caps on shared resource consumption — they exist because of multi-tenancy, not because of poor design choices
  • The six primary categories: CPU time, heap size, SOQL queries, DML statements, rows retrieved, and async calls — each applies per transaction, not per record
  • Bulkification — never querying or writing inside a loop — is the foundational Salesforce architecture principle that prevents the vast majority of limit violations
  • Flows and Apex share the same limit budget within a transaction — adding Flows to heavily-automated objects can push existing Apex code over the limit
  • Async processing (Future, Queueable, Batch) provides higher limits but should be designed from the start, not added as a fix
  • Governor Limit exposure should be a standing agenda item on your programme's architecture risk register from discovery through to hypercare

Checkpoint: Test Your Understanding

1. A trigger fires on a batch of 200 Opportunity records. Each record requires one SOQL query to retrieve related data. If the trigger is not bulkified, what happens?

A. Salesforce automatically optimises the queries to stay within limits
B. The trigger fails with a LimitException after the 100th query, leaving the remaining 100 records unprocessed
C. The trigger processes only the first 100 records and silently ignores the rest
D. The trigger runs but logs a performance warning in Salesforce's monitoring tools

2. A programme has a heavily automated Opportunity object with three existing triggers. A new record-triggered Flow is being added. What should the architect assess before approving this addition?

A. Whether the Flow's logic could be implemented as an additional trigger instead
B. The current Governor Limit consumption profile of the existing triggers, to determine whether adding the Flow's queries and DML will push the combined transaction over any limit threshold
C. Whether Flows have separate Governor Limit allocations from triggers (they do not)
D. Whether the Salesforce org has a performance add-on license

3. What is the correct architectural response when synchronous Apex genuinely cannot complete its required work within the 10,000ms CPU limit?

A. Request a Governor Limit exception from Salesforce for the specific org
B. Reduce the batch size to process fewer records per transaction
C. Redesign the work to run asynchronously (Queueable or Batch Apex), which provides a 60,000ms CPU limit in a separate transaction
D. Split the logic across multiple triggers to distribute the CPU consumption

Discussion & Feedback