System.LimitException: Too many SOQL queries: 101
Your code ran more than 100 SOQL queries in a single transaction. Almost always a SELECT inside a loop instead of one query whose results you iterate.
Also seen asToo many SOQL queries: 101·FATAL_ERROR System.LimitException: Too many SOQL queries: 101·LimitException: Too many SOQL queries
This is the most-Googled error in Salesforce history, and the cause is almost always the same: a SELECT sitting inside a for loop. Salesforce caps a single synchronous transaction at 100 SOQL queries; query 101 throws this LimitException and the whole transaction rolls back.
The shape of the bug
// One query per Account. The moment Trigger.new has 101 records, you are dead.
for (Account a : Trigger.new) {
List<Contact> kids = [SELECT Id FROM Contact WHERE AccountId = :a.Id];
a.Contact_Count__c = kids.size();
}
The shape of the fix
Lift the query out of the loop. Query once, group by parent ID into a Map, then iterate.
Set<Id> accountIds = new Map<Id, Account>(Trigger.new).keySet();
Map<Id, Integer> countByAccount = new Map<Id, Integer>();
for (AggregateResult ar : [
SELECT AccountId, COUNT(Id) c
FROM Contact
WHERE AccountId IN :accountIds
GROUP BY AccountId
]) {
countByAccount.put((Id) ar.get('AccountId'), (Integer) ar.get('c'));
}
for (Account a : Trigger.new) {
Integer c = countByAccount.get(a.Id);
a.Contact_Count__c = c == null ? 0 : c;
}
One query, regardless of trigger size. This pattern is called bulkification and it is the single most important habit to build in Apex.
When 100 still isn't enough
If you legitimately need more than 100 queries in one logical operation, you have three escape hatches:
- Batch Apex. Each chunk processed by
execute()gets its own fresh 100-query budget. - Asynchronous queueables chained together. Each
enqueueJobstarts a new transaction. - A different query. Often "I need 101 queries" really means "I haven't joined the data correctly" — a relationship query or
GROUP BYcollapses the problem.
Diagnose it from a debug log
Reproduce in a sandbox, open the failing transaction in Setup → Debug Logs, and search for SOQL_EXECUTE_BEGIN. Count them. If the same query string repeats dozens of times with only the bind parameter changing, you found your loop.
You can also instrument the running code:
System.debug('SOQL used: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
A nuance most people miss
The 100 cap is per transaction, not per script. A single Database.insert(records) can cascade into half a dozen triggers, workflow updates, and process automations — and they all share the same 100-query budget. So the runaway query may not be in your code at all; it may be in a trigger your code accidentally daisy-chained into. When you can't find the loop locally, look one level out at what your DML kicks off.
