Skip to main content

How to Fix "Too Many SOQL Queries: 101" in Salesforce: Complete Optimization Guide

In plain words: Think of a SOQL query like ordering items from a warehouse. Placing an order for each item individually (SOQL in a loop) takes massive overhead and quickly exhausts your delivery quota. Bulkification means combining everything into a single bulk shopping list (IN :idSet) so you make one fast, efficient trip to the database.

Salesforce executes database transactions within a multi-tenant cloud environment protected by strict runtime limits. One of the most critical boundaries is the SOQL query limit. When unoptimized code exceeds this allocation, the runtime engine throws an uncatchable System.LimitException: Too many SOQL queries: 101, immediately halting execution and rolling back uncommitted database work. Designing high-performance Apex requires understanding why queries accumulate and implementing proven bulkification patterns.

1. Understanding the Impact & Governor Limits

In Salesforce, governor limits apply to every synchronous and asynchronous transaction to ensure fair resource allocation across all tenants on a shared instance:

360 SOQL Limits & Performance Quick Card:
  • Synchronous SOQL Limit: Maximum 100 queries per transaction.
  • Asynchronous SOQL Limit: Maximum 200 queries per transaction (Batch, Queueable, Scheduled).
  • Total Records Retrieved: Maximum 50,000 records across all queries in a single execution context.
  • Uncatchable Exception: A LimitException cannot be caught in a try-catch block; the entire transaction fails immediately.

2. Common Root Causes of Excessive Queries

Most SOQL limit exceptions originate from a few predictable coding anti-patterns:

  • Queries Inside Loops: Placing a [SELECT ...] query directly inside a for or while loop causes the query count to scale linearly with the number of records processed.
  • Trigger Cascades & Recursion: Field updates inside triggers that trigger downstream workflow rules, record-triggered flows, and secondary triggers, repeatedly firing queries on each cycle.
  • Unused Relationship Queries: Executing separate queries for parent and child records instead of using parent-to-child subqueries or child-to-parent relationship traversals.

3. Practical Optimization Patterns & Code Fixes

Eliminating SOQL limits requires transforming iterative queries into collection-driven bulk queries combined with in-memory Map lookups.

Pattern 1: Bulkification via Sets and Maps (Replacing Loop Queries)
Collecting parent IDs into a Set, executing one single SOQL query, and mapping records for instant O(1) retrieval:
// ❌ Anti-Pattern: Triggers SOQL 101 when processing > 100 Contacts
public static void updateAccountIndustriesBad(List<Contact> conList) {
    for (Contact con : conList) {
        if (con.AccountId != null) {
            Account acc = [SELECT Id, Industry FROM Account WHERE Id = :con.AccountId LIMIT 1];
            // Process account...
        }
    }
}

// Bulkified Pattern: Consumes exactly 1 SOQL query regardless of list size
public static void updateAccountIndustriesGood(List<Contact> conList) {
    Set<Id> accountIds = new Set<Id>();
    for (Contact con : conList) {
        if (con.AccountId != null) {
            accountIds.add(con.AccountId);
        }
    }

    if (accountIds.isEmpty()) {
        return;
    }

    // Single query using Set binding with User Mode security
    Map<Id, Account> accountMap = new Map<Id, Account>([
        SELECT Id, Industry 
        FROM Account 
        WHERE Id IN :accountIds 
        WITH USER_MODE
    ]);

    for (Contact con : conList) {
        if (con.AccountId != null && accountMap.containsKey(con.AccountId)) {
            Account parentAcc = accountMap.get(con.AccountId);
            // Process account safely in memory...
        }
    }
}
Pattern 2: Leveraging Relationship Queries (Parent-Child & Child-Parent)
Fetching related child records (Contacts) alongside the parent Account in a single query:
// Fetching Accounts and their related child Contacts simultaneously
List<Account> accountsWithContacts = [
    SELECT Id, Name, (SELECT Id, FirstName, LastName, Email FROM Contacts) 
    FROM Account 
    WHERE Id IN :accountIds 
    WITH USER_MODE
];

for (Account acc : accountsWithContacts) {
    // Child records are available in memory without additional queries
    for (Contact con : acc.Contacts) {
        System.debug('Contact: ' + con.LastName + ' on Account: ' + acc.Name);
    }
}

4. Advanced Query Optimization & Tooling

  • Query Plan Tool in Developer Console: Open the Developer Console, click Query Grid, and select Query Plan to evaluate the cost of your SOQL filters, identify index usage, and detect full-table scans.
  • Enforce SOQL Selectivity: Always filter queries using indexed standard fields (Id, Name, lookup relationship fields) or custom fields marked as External ID or Unique to ensure query optimization on large data volumes.
  • Offload to Asynchronous Apex: When synchronous transactions approach query or CPU limits, delegate heavy operations to Queueable Apex or Database.Batchable to double your SOQL query limit from 100 to 200.
  • Use Platform Cache: Store frequently read, rarely changed configuration data or reference sets in the Salesforce Platform Cache to eliminate redundant SOQL queries entirely.

5. Critical Traps & Best Practices

Developer Trap: SOQL Inside Trigger Helper Methods
Even if a query is not directly inside a visible loop, calling a helper method that contains a SOQL query from inside a for loop will still trigger a LimitException. Always ensure utility and helper methods accept full collections rather than individual record parameters.
Core Rule: Never execute SOQL queries inside loops or helper methods called from loops. Always collect record IDs into Sets, query once using WHERE Id IN :idSet, and store results in a Map.
  • Proactive Capacity Monitoring: Check remaining query allocations using Limits.getQueries() and Limits.getLimitQueries() before executing optional dynamic queries.
  • Prevent Trigger Recursion: Use static Boolean flags or standardized trigger handler frameworks to avoid duplicate trigger executions and redundant query runs.
  • Enforce Object & Field Security: Query using the WITH USER_MODE clause to respect Object-Level and Field-Level Security natively.

Summary

Resolving excessive SOQL query issues is essential for building scalable, enterprise-grade applications in Salesforce. By replacing loop queries with bulkified Map patterns, leveraging parent-child relationship subqueries, ensuring selective index filtering, and offloading heavy processing to asynchronous Apex, you eliminate Governor Limit exceptions and ensure consistent performance across your org.