Salesforce · Error fix

System.LimitException: Too many SOQL queries: 101 - why it happens and how to fix it

What the Too many SOQL queries: 101 error means, the five places it hides, how to find the query in a debug log, and the fixes - with code you can copy.

Level: BeginnerRead: 11 minIncludes: code + interactive demo
Bhanu PratapSalesforce Developer · Updated 25 October 2026

It always seems to happen at the worst moment. The code worked in the sandbox, the tests passed, and then a data load or an integration runs in production and the screen fills with red: System.LimitException: Too many SOQL queries: 101. Nothing is saved, and the error does not even say which line caused it.

This guide takes the error apart in four steps: what it is, why it happens, a real example of finding and fixing it, and a short summary to keep.

Step 1 · What is it

What "Too many SOQL queries: 101" means

Every Apex transaction may run 100 SOQL queries when it runs synchronously - while a user waits on a save, a button or a page. The moment code asks for query number 101, Salesforce stops it with System.LimitException: Too many SOQL queries: 101.

Two things make this error painful. You cannot catch it with try/catch, and Salesforce rolls back everything the transaction did - every insert, update and email. The only fix is code that never asks for the 101st query.

Real-life exampleYou are sent to the shop for 200 things on a list. You can make the trip once with the whole list, or walk there 200 times, one item each. Salesforce allows 100 trips. The 101st trip is refused, and everything you already brought home is taken back.

In background (asynchronous) code - Batch, Queueable, @future - the limit is 200, so the same mistake shows up as Too many SOQL queries: 201. The cause and the fix are the same.

From my experience

This is the error I have hit most often in real Salesforce projects. Almost every time, the code looked fine and passed its tests with one record, then failed the first time a data load or an integration sent 200 records together. The query was rarely in plain sight - it sat inside a helper method, or a Flow and a trigger were quietly sharing the same 100 queries.

Step 2 · Why it happens

The five usual causes

The error says how many queries ran, not where they came from. In practice it is almost always one of these five.

1

A query inside a for loop

the classic

The loop runs once per record, so the query does too. A trigger receives up to 200 records at a time, which is 200 queries.

Wrong: one query per contact
trigger ContactTrigger on Contact (before insert) {
    for (Contact c : Trigger.new) {
        Account a = [SELECT Region__c FROM Account WHERE Id = :c.AccountId];
        c.Region__c = a.Region__c;
    }
}
2

A query hidden in a helper method

the sneaky one

The loop looks clean because the query is one method call away. It is still one query per record.

The query you cannot see in the loopTriggerfor (Contact c : new)runs 200 timesHelper methodgetRegion(c)looks harmlessSOQL query[SELECT ... ]once per contact200 contacts = 200 queries: fails at query 101
Wrong: the loop calls a method that queries
for (Contact c : Trigger.new) {
    // getRegion runs a query - once per contact
    c.Region__c = AccountService.getRegion(c.AccountId);
}
3

Triggers that fire other triggers

one save, many triggers

Updating an Opportunity fires its trigger, which updates the Account, which fires the Account trigger, which updates Contacts... Every trigger in the chain draws from the same 100. Each one can look efficient on its own and still push the total over the limit - and recursion, a trigger firing itself again, doubles it.

4

Flows share the same budget

Flow + Apex

A record-triggered flow runs in the same transaction as your Apex trigger. Every Get Records element is a SOQL query, and a Get Records inside a Loop element runs once per item - the Flow version of cause 1.

5

One big DML statement

1,000 records, one budget

Insert 1,000 records with one Apex DML statement and the trigger runs 5 times, 200 records each - but all 5 runs share one transaction. A trigger that uses 25 queries per run uses 125 in total and fails on the fifth run, even though it handles 200 records perfectly.

Step 3 · Example

Find the query that repeats

Take the trigger from cause 1: it copies the region from each new contact's account. It passes every test with one contact, then fails when a data load inserts 200. Here is how you would track it down and fix it.

  1. Open Setup → Debug Logs and add a trace flag for your user (or the integration user), with the Database category set to INFO or finer.
  2. Run the action again - the save, the data load or the button.
  3. Open the log and search for SOQL_EXECUTE_BEGIN. Every query writes one line, with the class and line number it came from.
  4. The query you see over and over, from the same line, is your culprit. The end of the log also shows Number of SOQL queries: 101 out of 100.

While you are fixing it, print the count at the points you suspect. The Limits class tells you how many queries have run so far in this transaction.

A fuel meter for queries · Apex
System.debug('SOQL used: ' + Limits.getQueries() + ' of ' + Limits.getLimitQueries());
Step 3 · Example

Fix it - five ways, most common first

Fix 1: collect the Ids, query once, use a Map

Loop once to collect what you need, run one query for all of it, then loop again and read from a Map. 200 records now cost one query.

Wrong: a query per record
for (Contact c : Trigger.new) {
    Account a = [SELECT Region__c FROM Account WHERE Id = :c.AccountId];
    c.Region__c = a.Region__c;
}
Right: one query for all records
Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) accountIds.add(c.AccountId);

Map<Id, Account> accounts = new Map<Id, Account>(
    [SELECT Region__c FROM Account WHERE Id IN :accountIds]);

for (Contact c : Trigger.new) {
    Account a = accounts.get(c.AccountId);
    if (a != null) c.Region__c = a.Region__c;
}
Wrong: 200 contacts, 200 queries200 contactsDatabaseFails at query 101: Too many SOQL queriesRight: collect Ids, then 1 query200 contactsSet of Account IdsDatabasecollect1 query
Try it yourself

SOQL limit simulator

Move the slider to save more records at once, and see when each style of code breaks.

Code style
50of 100 queries
Passes
It works today, but it breaks the first time more than 100 records are saved together.

Fix 2: make helper methods take a list

A method that takes one Id and returns one value invites being called in a loop. Give it a Set of Ids and have it return a Map - then it can only be called once.

AccountService.cls - bulk-safe by design
public with sharing class AccountService {
    public static Map<Id, String> getRegions(Set<Id> accountIds) {
        Map<Id, String> regions = new Map<Id, String>();
        for (Account a : [SELECT Region__c FROM Account WHERE Id IN :accountIds]) {
            regions.put(a.Id, a.Region__c);
        }
        return regions;
    }
}

Fix 3: get related records in the same query

Instead of querying Contacts once per Account, ask for the Accounts with their Contacts in one query. The outer query counts as one SOQL query; the subquery counts toward a separate, larger limit.

Parents and children together
for (Account a : [SELECT Id, Name, (SELECT Id, Email FROM Contacts)
                  FROM Account WHERE Id IN :accountIds]) {
    for (Contact c : a.Contacts) {
        // no extra query
    }
}

Fix 4: stop the same trigger running twice

If the debug log shows your trigger's queries twice in one save, the trigger is firing itself again. Remember which records were handled in a static Set, and skip them the second time.

A recursion guard
public class TriggerGuard {
    public static Set<Id> handled = new Set<Id>();
}

// in the after update handler
List<Account> todo = new List<Account>();
for (Account a : (List<Account>) Trigger.new) {
    if (TriggerGuard.handled.add(a.Id)) todo.add(a); // false if already seen
}

Fix 5: fix the Flow too

Move any Get Records out of a Loop element: get all the records once before the loop, then use a collection or a filter inside it. And check whether the Flow and an Apex trigger are both doing work on the same object - often one can do the job of both.

Remember: Moving code to a Queueable job raises the limit to 200, but a query in a loop still fails at 201 records. Async is not the fix - bulkifying is.

Step 3 · Example

Prove the fix with a test

Most SOQL 101 errors reach production because the tests use one record. Insert 200, run the code between Test.startTest() and Test.stopTest() so it gets fresh limits, and check how many queries it used.

ContactTriggerTest.cls
@isTest
private class ContactTriggerTest {
    @isTest
    static void twoHundredContactsUseFewQueries() {
        Account acc = new Account(Name = 'Acme', Region__c = 'North');
        insert acc;
        List<Contact> cons = new List<Contact>();
        for (Integer i = 0; i < 200; i++) {
            cons.add(new Contact(LastName = 'Test ' + i, AccountId = acc.Id));
        }

        Test.startTest();
        insert cons;
        Integer used = Limits.getQueries();
        Test.stopTest();

        Assert.isTrue(used < 5, 'Trigger is not bulk-safe: ' + used + ' queries');
        Assert.areEqual(200, [SELECT COUNT() FROM Contact WHERE Region__c = 'North']);
    }
}
Step 4 · Short summary

The short version

Too many SOQL queries: 101 means one transaction asked for more than its 100 queries - almost always because a query runs once per record, in a loop, a helper method, a chain of triggers or a Flow. Find the repeating line in the debug log, collect the Ids and query once into a Map, and prove it with a test that inserts 200 records.

  • No SOQL in loopsCollect Ids first, query once.
  • Look inside method callsA helper called in a loop is a query in a loop.
  • Helpers take setsSet<Id> in, Map out - impossible to misuse.
  • Use subqueriesParents and children in one query.
  • Guard recursionA static Set of handled Ids.
  • Count the whole saveTriggers, Flows and other triggers share 100.
  • Async is not a fixIt moves the limit to 200, not away.
  • Test with 200 recordsAnd assert on Limits.getQueries().
Quick answers

Questions people ask about this error

Can I increase the 100 SOQL query limit?

No. Governor limits are fixed for every org and cannot be raised by Salesforce support. The code has to use fewer queries.

Can I catch Too many SOQL queries: 101 with try/catch?

No. A LimitException cannot be caught. The transaction stops and every change in it is rolled back.

Why does my error say 201 instead of 101?

The code is running asynchronously - in Batch, Queueable or @future - where the limit is 200 queries. The cause and the fix are the same.

Does a SOQL for loop count as one query?

Yes. for (Account a : [SELECT ...]) is one query, even though Salesforce hands you the rows in chunks of 200.

Do Flow Get Records elements count?

Yes. Each Get Records is a SOQL query, and it shares the same 100 with every Apex trigger in the transaction.

Does reading Custom Metadata count?

Not when you use the getInstance or getAll methods - they do not use SOQL queries. A SOQL query on a __mdt type does count.

Read nextSalesforce Governor Limits, explained simplyEvery governor limit in plain words - DML, CPU time, heap and more - with the fix for each.

Educational only. This article explains how things work. It is not investment, tax, legal or career advice, and it is not a recommendation to buy or sell anything. What suits you depends on your own situation - check with a qualified adviser before acting on money decisions.