If there's one concept that separates "knows Apex syntax" from "can write production Salesforce code," it's governor limits. This guide explains why they exist and how to design around them.
Why Governor Limits Exist
Salesforce runs on multi-tenant infrastructure — many customers share the same underlying compute resources. Without limits, one customer's inefficient code (say, an unbounded loop making thousands of database calls) could degrade performance for every other customer on the same infrastructure. Governor limits are Salesforce's way of guaranteeing fair, predictable resource usage across all tenants.
The Most Commonly Hit Limits
- SOQL queries per transaction — a cap on how many SOQL queries can run within a single transaction
- DML statements per transaction — a cap on how many DML operations (insert/update/delete) can run within a single transaction
- Records processed by DML per transaction — how many total records a transaction can insert/update/delete
- CPU time per transaction — how much processing time a transaction can consume before timing out
- Heap size per transaction — how much memory a transaction can use
(Exact numeric values change across Salesforce releases — always check the current official limits documentation rather than relying on a number you memorized from an older source.)
The Root Cause of Almost Every Governor Limit Error
Code that isn't bulk-safe. Nearly every governor limit violation traces back to logic written assuming "this runs on one record," when Salesforce always processes DML in batches (e.g., 200 records from a Data Loader import, or a bulk API call).
The classic failure pattern:
// This works fine for 1 record, and fails at scale
for (Opportunity opp : Trigger.new) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :opp.AccountId];
// ...
}
With 1 record, this runs 1 query. With 200 records in a bulk operation, it runs 200 queries — quickly exceeding the per-transaction SOQL limit.
How to Write Bulk-Safe Code
- Never put SOQL or DML inside a loop. Query and perform DML once, outside loops, using collections (Sets/Maps) to gather what you need first.
- Use Maps keyed by Id to efficiently look up related data without repeated queries.
- Batch large data operations using Batch Apex, which is purpose-built to process large volumes within governor-limit-safe chunks.
- Move heavy processing to asynchronous Apex (Queueable, Future, Batch) when synchronous limits are too tight for the workload — async contexts get their own, often higher, limit allocations.
- Test with realistic bulk data, not just single records — a class that passes tests with one record can still fail in production at scale if it was never tested in bulk.
A Mental Model That Helps
Whenever you write Apex, ask: "What happens if this runs for 200 records at once instead of 1?" If the answer involves 200 queries, 200 DML statements, or unbounded processing, redesign it before you consider the code done — not after it fails in production.
Learning This Properly
Governor limits are best understood by actually hitting them (in a safe learning environment) and fixing the code — not just reading about them abstractly. This is a core part of the Apex fundamentals module in our Salesforce Developer Training course.