Iterable<sObject> is the alternative return type for a batch start method. Use it when your dataset can’t be expressed as a single SOQL query — for example, a list merged from multiple queries, records computed from an external API, or a custom in-memory collection.
The trade-off: capped at 50,000 records (standard SOQL governor applies), versus 50 million for QueryLocator.
The pattern
public class CustomSourceBatch implements Database.Batchable<sObject> {
public Iterable<sObject> start(Database.BatchableContext ctx) {
// Build the list however you want
List<Account> activeAccounts = [SELECT Id FROM Account WHERE Active__c = true];
List<Account> recentAccounts = [SELECT Id FROM Account WHERE CreatedDate = LAST_N_DAYS:7];
// Merge + dedupe
Map<Id, Account> merged = new Map<Id, Account>();
for (Account a : activeAccounts) merged.put(a.Id, a);
for (Account a : recentAccounts) merged.put(a.Id, a);
return merged.values();
}
public void execute(Database.BatchableContext ctx, List<Account> scope) {
// ... process the chunk
}
public void finish(Database.BatchableContext ctx) {}
}
merged.values() returns a List<Account>, which satisfies Iterable<sObject> (every Apex list implements Iterable).
A truly custom iterator
For very custom data sources, you can build your own iterator:
public class CountdownIterator implements Iterator<sObject>, Iterable<sObject> {
private Integer cur;
public CountdownIterator(Integer start) { this.cur = start; }
public Boolean hasNext() { return cur > 0; }
public sObject next() {
Account a = new Account(Name = 'Account ' + cur);
cur--;
return a;
}
public Iterator<sObject> iterator() { return this; }
}
public class CountdownBatch implements Database.Batchable<sObject> {
public Iterable<sObject> start(Database.BatchableContext ctx) {
return new CountdownIterator(100);
}
// ...
}
This is rare in practice — most teams use the simpler “build a list, return it” pattern.
QueryLocator vs Iterable side-by-side
Database.QueryLocator | Iterable<sObject> | |
|---|---|---|
| Max records | 50,000,000 | 50,000 |
| Source | Single SOQL query | Anything |
| Memory profile | Streamed, low memory | All loaded upfront |
| Composite sources (multi-query) | No | Yes |
| Required when SOQL alone isn’t enough | — | Yes |
| Performance for huge datasets | Excellent | Poor (hits memory) |
When Iterable is the right choice
- Records merged from multiple queries that you want to dedupe before batching.
- External API results — fetch the records via callout (in a prep step), then iterate.
- Calculated records — e.g., generate placeholder records on the fly.
- Records filtered with logic SOQL can’t express — though usually you can find a SOQL form.
When QueryLocator wins
- Anything over 50,000 records.
- Single SOQL query is enough.
- You want the platform to stream records efficiently.
Common interview follow-ups
- Can I return
Iterable<MyCustomType>? — Yes, if you declared the classimplements Database.Batchable<MyCustomType>. Most code usessObjectthough. - Does Iterable bypass the 50K SOQL limit? — No. That’s the whole reason QueryLocator exists.
- What’s the practical max for Iterable? — Whatever fits in heap. The 50K SOQL ceiling is the hard wall, but you’ll likely hit heap (6 MB sync / 12 MB async) first on large datasets.
Verified against: Apex Developer Guide — Using Batch Apex. Last reviewed 2026-05-17.