Skip to main content

SF-0368 · Concept · Medium

Explain about Database.Querylocator in batch apex?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

Database.QueryLocator is the standard return type from a batch start method. It represents the result of a SOQL query, but with a special trick: it can return up to 50 million records — bypassing the synchronous SOQL governor cap of 50,000 rows.

How you create one

public Database.QueryLocator start(Database.BatchableContext ctx) {
    return Database.getQueryLocator(
        'SELECT Id, Email FROM Contact WHERE Active__c = true'
    );
}

You can pass either a SOQL string or a dynamic query built with bind variables:

String soql = 'SELECT Id, Status FROM Case WHERE Status = :statusFilter';
return Database.getQueryLocator(soql);

(The bind only works if statusFilter is in scope; the safer form is to use static SOQL where possible.)

Why use it over Iterable<sObject>?

CapabilityDatabase.QueryLocatorIterable<sObject>
Max records50,000,00050,000
SourceSingle SOQL queryAnything (in-memory list, calculated set)
StreamingYes (chunked by the platform)All loaded at once
PerformanceHighLimited by memory
FOR UPDATENoN/A

If a single SOQL query can express your dataset, always use QueryLocator. It’s the only path to truly large datasets.

How chunking works

Salesforce internally treats the QueryLocator like a cursor. When the batch runs:

  1. start returns the locator (the query is not fully executed yet).
  2. The platform pages through results in chunks of your chosen size (default 200).
  3. Each chunk is passed to a fresh execute invocation in its own transaction.

You never see all 50 million records loaded into memory at once — the platform streams them.

Limits to know

LimitValue
Records returned by QueryLocator50,000,000
Concurrent batch jobs5 (queued past that go to Flex Queue)
Chunk size range1–2,000 records
Default chunk size200
SOQL row count in startBypassed — QueryLocator is exempt

What it can and can’t do

  • Can use ORDER BY, WHERE, joins, sub-selects (carefully).
  • Can be combined with Database.Stateful.
  • Cannot use FOR UPDATE (the chunk transactions don’t share a lock).
  • Cannot include aggregate queries (COUNT(), GROUP BY etc.) — those return AggregateResult, not sObjects.

When to use Iterable instead

Pick Iterable<sObject> when your dataset isn’t a single SOQL query — e.g., records computed from an external API or merged from multiple queries with deduping:

public Iterable<sObject> start(Database.BatchableContext ctx) {
    // ... build custom list (≤ 50,000)
    return customList;
}

You lose the 50M ceiling, but you gain flexibility.

Common interview follow-ups

  • What does Database.getQueryLocator return when called outside a batch? — A Database.QueryLocator object, but useless without a batch context. Most apps never call it standalone.
  • Can the QueryLocator’s SOQL include sub-queries? — Yes, but the relationship sub-query has its own 200-row cap per parent record. Use cautiously.
  • Does QueryLocator count against synchronous SOQL limits? — No — it’s the whole point.

Verified against: Apex Developer Guide — Database.QueryLocator Class. Last reviewed 2026-05-17.