System.LimitException: Too many SOQL queries: 101
What does this error mean?
This error occurs when Apex code attempts to execute more than 100 SOQL queries within a single synchronous transaction. Salesforce enforces this governor limit to prevent any single tenant from monopolizing shared database resources on the multi-tenant platform.
The limit is 100 SOQL queries for synchronous operations and 200 for asynchronous operations (Batch, Queueable, Future, Scheduled).
Common Causes
1. SOQL Queries Inside Loops
The most common cause. If you place a SOQL query inside a for loop, the query executes once per iteration. With 200 records, you hit the limit immediately.
// ❌ BAD: Query inside a loop
for (Account acc : Trigger.new) {
// This runs once per record!
List<Contact> contacts = [
SELECT Id, Name
FROM Contact
WHERE AccountId = :acc.Id
];
}
2. Recursive Triggers
Triggers that update related records can cause other triggers to fire, each with their own SOQL queries, cascading until the limit is hit.
3. Inefficient Helper Methods
Helper or utility methods that perform queries internally, called repeatedly from trigger handlers or batch classes.
How to Fix It
Solution 1: Move Queries Outside the Loop (Bulkification)
Collect all the IDs first, then make a single SOQL query outside the loop. Use a Map to associate the results back.
// ✅ GOOD: Bulkified — single query
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) {
accountIds.add(acc.Id);
}
// One query, all results
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [
SELECT Id, Name, AccountId
FROM Contact
WHERE AccountId IN :accountIds
]) {
if (!contactsByAccount.containsKey(c.AccountId)) {
contactsByAccount.put(c.AccountId, new List<Contact>());
}
contactsByAccount.get(c.AccountId).add(c);
}
// Now loop uses the Map — no queries
for (Account acc : Trigger.new) {
List<Contact> contacts = contactsByAccount.get(acc.Id);
// Process contacts...
}
Solution 2: Use Relationship Queries
Combine parent and child queries into a single SOQL statement using relationship (subquery) syntax.
// ✅ Single query with subquery
List<Account> accounts = [
SELECT Id, Name,
(SELECT Id, Name, Email FROM Contacts)
FROM Account
WHERE Id IN :Trigger.newMap.keySet()
];
Solution 3: Prevent Trigger Recursion
public class TriggerHandler {
private static Boolean hasRun = false;
public static Boolean hasAlreadyRun() {
return hasRun;
}
public static void setHasRun() {
hasRun = true;
}
}
Pro Tip: Use Limits.getQueries() and Limits.getLimitQueries() in your debug logs to monitor how many SOQL queries your code uses per transaction.