System.LimitException: Too Many SOQL Queries — What It Means and How to Fix It

If you’ve been working with Salesforce Apex for any amount of time, you’ve almost certainly run into this error at least once:

System.LimitException: Too many SOQL queries: 101

In this tutorial, I’ll walk you through what this error means, why Salesforce enforces it, what causes it in real code, and — most importantly — how to fix the error: System.LimitException: Too Many SOQL Queries, with practical examples you can use right away.

What Is This “System.LimitException: Too Many SOQL Queries” Error, Exactly?

Salesforce runs on a multi-tenant architecture. That means thousands of companies share the same infrastructure. To make sure one org’s runaway code doesn’t choke the entire platform, Salesforce enforces something called Governor Limits.

One of those limits is:

You can only run 100 SOQL queries in a single synchronous transaction. For asynchronous (like Batch Apex), that limit goes up to 200.

Too Many SOQL Queries in Salesforce Apex

The moment your code tries to fire query number 101, Salesforce throws System.LimitException: Too many SOQL queries (101), so the entire transaction rolls back. It doesn’t warn you. It just stops.

This isn’t a soft warning you can ask Salesforce Support to increase. It’s a hard stop. There’s no way around the limit itself — the only fix is writing better code.

Why Does System.LimitException Usually Happens in Apex?

In my experience, this error almost always comes down to one of three root causes:

  • SOQL queries inside a for loop — the most common culprit by far
  • Recursive triggers — a trigger calls itself repeatedly, running queries on every cycle
  • Too many automations stacking up — Flows, Process Builders, and Apex all running in the same transaction, each with its own queries

Let me go through each one with real code examples.

Cause 1: Using SOQL Query Inside a For Loop in Apex Trigger

This is the classic mistake. You’ve got a list of records, you loop through them, and inside the loop, you run a query to fetch related data. Seems logical, right? Wrong.

Here’s what the bad version looks like:

trigger AccountTriggers on Account (after insert) {

for (Account acc : Trigger.new) {

// BAD PRACTICE: Query inside loop

List<Contact> contacts = [
SELECT Id, Name, Email
FROM Contact
WHERE AccountId = :acc.Id
];

for(Contact con : contacts) {
System.debug('Contact Name: ' + con.Name);
}
}
}

To debug the trigger above, open the Execute Anonymous Window and run the following code.

List<Account> accList = new List<Account>();

for(Integer i = 0; i < 200; i++){

    Account acc = new Account(
        Name = 'Test Account ' + i
    );

    accList.add(acc);
}

insert accList;

If your trigger fires on 200 Account records, this code will try to run 200 separate SOQL queries. It works fine in the developer sandbox with 5 records.

But the moment a data load pushes 101+ records through in production, it blows up.

Too Many SOQL Queries in Salesforce Apex

Fix Error: Query Once, Use a Map in Apex

The solution is to pull all the data you need in a single query before the loop, store it in a Map, and then reference that map inside the loop.

trigger AccountTriggers on Account (after insert) {

Set<Id> accountIds = new Set<Id>();

// Step 1: Collect all Account IDs

for(Account acc : Trigger.new){
accountIds.add(acc.Id);
}

// Step 2: Query all related contacts in ONE query

List<Contact> contactList = [
SELECT Id, Name, Email, AccountId
FROM Contact
WHERE AccountId IN :accountIds
];

// Step 3: Store contacts grouped by AccountId

Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();

for(Contact con : contactList){

if(!contactsByAccount.containsKey(con.AccountId)){
contactsByAccount.put(con.AccountId, new List<Contact>());
}

contactsByAccount.get(con.AccountId).add(con);
}

// Step 4: Use the map inside the loop

for(Account acc : Trigger.new){

List<Contact> relatedContacts = contactsByAccount.get(acc.Id);

if(relatedContacts != null){

for(Contact con : relatedContacts){
System.debug('Contact Name: ' + con.Name);
}
}
}
}

That’s it. One query instead of 200. This is called bulkification, and it’s the single most important habit to develop as a Salesforce developer.

Cause 2: DML and Queries Both Inside a Loop in Apex

Sometimes I see code where developers not only query inside a loop but also run DML (like update or insert) inside the same loop. This doubles down on the problem — you’re burning through both the SOQL limit (100) and the DML limit (150) at the same time.

Here’s a real example of what to avoid — updating Case records:

public class CaseStatusUpdaterBad {

public static void updateCases(){

// Query cases

List<Case> caseList = [
SELECT Id, Status
FROM Case
WHERE Status = 'New'
LIMIT 105
];

// BAD PRACTICE

for(Case c : caseList){

c.Status = 'Working';

update c; // DML inside loop
}

}
}

This code runs an UPDATE statement 105 times inside the loop. That’s going to hit limits fast.

Fix Error: Move DML Outside the Loop in Apex

public class CaseStatusUpdater {

public static void updateCases(){

List<Case> caseList = [
SELECT Id, Status
FROM Case
WHERE Status = 'New'
LIMIT 105
];

// Modify records in memory

for(Case c : caseList){
c.Status = 'Working';
}

// Perform ONE DML operation

if(!caseList.isEmpty()){

try{
update caseList;
}
catch(DmlException e){
System.debug('Error updating cases: ' + e.getMessage());
}
}
}
}

The loop only modifies the in-memory objects. The update run exactly once after the loop finishes. Clean, efficient, and well within limits.

Cause 3: Recursive Triggers in Apex

Recursive triggers are sneaky. Your trigger fires, makes a change, which fires the trigger again, which makes a change, which fires it again… until it hits the SOQL limit.

Say you have a trigger on the Contact object that updates a related Account. If that Account update triggers another Apex class that queries Contacts again, you’re in a loop.

Recursive triggers happen when:

  1. A trigger updates a record
  2. That update fires the same trigger again
  3. Which performs another update
  4. And the cycle continues

Eventually Salesforce throws:

Fix Error: Static Boolean Guard

The standard approach is to use a static Boolean variable in a helper class to make sure the trigger only executes once per transaction:

// TriggerHelper.cls

public class TriggerHelper {

public static Boolean hasRun = false;

}

// ContactTrigger.trigger

trigger ContactTrigger on Contact (after update) {

if(!TriggerHelper.hasRun){

TriggerHelper.hasRun = true;

List<Account> accountsToUpdate = new List<Account>();

for(Contact con : Trigger.new){

if(con.AccountId != null){

Account acc = new Account(
Id = con.AccountId,
Description = 'Contact was updated'
);

accountsToUpdate.add(acc);
}
}

if(!accountsToUpdate.isEmpty()){
update accountsToUpdate;
}
}
}

Because static variables persist for the duration of a transaction, hasRun stays true after the first execution, and the trigger skips all subsequent recursive calls.

Cause 4: Salesforce Flow Executing Multiple Times

This one catches people off guard. Flows can also contribute to the 100-query limit, especially if:

  • A Flow has a Get Records element inside a loop
  • Multiple Flows are triggered on the same object
  • A Flow triggers an Apex class that also runs queries

The fix in Flows is similar to the Apex fix: retrieve all the records you need in a single Get Records element before the loop, then use Collection Filters within the loop rather than additional Get Records elements.

If you’re hitting limits in production but not in sandbox, this is often the cause — you simply don’t have enough records in sandbox to trigger the limit, but production data volume exposes the problem.

Salesforce Flow Executing Multiple Times

How to Debug This Error

When you get this error, don’t just start randomly moving code around. First, figure out where the queries are being fired.

Here’s how:

  1. Enable Debug Logs — Go to Setup → Debug Logs → add a log for the running user with APEX_CODE set to DEBUG level
  2. Reproduce the error — Run the transaction that causes it
  3. Open the debug log — Search for SOQL_EXECUTE_BEGIN entries. Each one represents a query being fired
  4. Count and trace — You’ll see the line numbers where each query fired. Look for any query that appears in a loop (you’ll see it fire repeatedly with different values)

You can also use System.Limits.getQueries() In your code, check how many queries have been fired so far in the transaction:

System.debug('SOQL queries used so far: ' + Limits.getQueries());
System.debug('SOQL queries remaining: ' + Limits.getLimitQueries() - Limits.getQueries());

Drop these debug lines before and after suspicious code blocks. They’ll tell you exactly where you’re burning through your query budget.

Best Practices to Avoid System.LimitException Error

Here’s a checklist I keep in mind every time I write Apex:

  • Never put SOQL queries inside a for loop — query once, use a map/list inside the loop
  • Never put DML statements inside a for loop — collect changes in a list, then run DML once after the loop
  • Use Map<Id, SObject> patterns to relate parent and child records efficiently
  • Add recursion guards to all triggers using static boolean variables
  • Use Limits.getQueries() to monitor query usage during development and testing
  • Move heavy processing to @future or Batch Apex when dealing with large record volumes
  • In Flows, avoid Get Records elements inside loops — get everything up front and filter in-memory
  • Write tests with bulk data — always test with 200 records in your unit tests, not just 1 or 5

Conslusion

The System.LimitException: Too many SOQL queries error looks scary, but it’s really Salesforce doing you a favor — it’s telling you that your code isn’t ready for real-world data volumes.

The fix almost always comes down to the same thing: stop querying inside loops, bulkify your code, and think in collections rather than individual records.

Once you internalize the “query once, use a map” pattern, you’ll start writing this way naturally and hit this error a lot less often.

You may like to read:

Agentforce in Salesforce

DOWNLOAD FREE AGENTFORCE EBOOK

Start with AgentForce in Salesforce. Create your first agent and deploy to your Salesforce Org.

Salesforce flows complete guide

FREE SALESFORCE FLOW EBOOK

Learn how to work with flows in Salesforce with 5 different real time examples.