DML or SOQL executed inside a loop

ID

apex.correctness.limits_in_loop

Severity

high

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Resource Management

Language

Apex

Description

Reports a DML statement, a query assigned inside a loop body, or a Database.query call written inside a for or while loop. A SOQL for-loop, which queries once and iterates the result in batches, is not reported - it is the recommended shape.

Rationale

Apex allows 100 SOQL queries and 150 DML statements per transaction, and a loop spends one per iteration: the code works on the two records a developer tests with and throws System.LimitException the first time a batch, an import or a bulk update passes 100 rows. The failure arrives in production, in bulk, and rolls back the whole transaction - which is why bulkification is the first thing a Salesforce review looks for.

The following code illustrates the pattern detected by this rule:

public class BulkSafety {

    public void unbulkified(List<Account> accts) {
        for (Account a : accts) {
            // FLAGGED: DML or SOQL executed inside a loop
            List<Contact> cs = [SELECT Id FROM Contact WHERE AccountId = :a.Id];
            // FLAGGED: DML or SOQL executed inside a loop
            update a;
        }
    }

    public void unbulkifiedWhile(List<Account> accts) {
        Integer i = 0;
        while (i < accts.size()) {
            // FLAGGED: DML or SOQL executed inside a loop
            insert new Task(WhatId = accts[i].Id);
            i++;
        }
    }

    public void soqlForLoop(Set<Id> accountIds) {
        List<Contact> toInsert = new List<Contact>();
        for (Account a : [SELECT Id, Name FROM Account WHERE Id IN :accountIds]) {
            toInsert.add(new Contact(LastName = a.Name));
        }
        insert toInsert;
    }

    public void bulkified(List<Account> accts) {
        Map<Id, Contact> byAccount = new Map<Id, Contact>();
        List<Task> tasks = new List<Task>();
        for (Account a : accts) {
            tasks.add(new Task(WhatId = a.Id));
        }
        insert tasks;
        System.debug(byAccount);
    }
}

Remediation

Move the query outside the loop and collect what it returns into a Map<Id, SObject> the loop reads from, and accumulate the records to change in a List that is written once after the loop with a single insert or update.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved