Every Batch Apex class implements Database.Batchable<sObject> and provides exactly three methods: start, execute, and finish. The names and signatures are fixed by the interface; each has a specific role in the batch lifecycle.
The three methods
public class MyBatch implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext ctx) { ... }
public void execute(Database.BatchableContext ctx, List<sObject> scope) { ... }
public void finish(Database.BatchableContext ctx) { ... }
}
1. start — gather the records
Runs once at the beginning of the job. Returns the records to process, either as:
Database.QueryLocator— when records come from a single SOQL query. Up to 50 million rows. Bypasses the standard 50,000-row SOQL ceiling.Iterable<sObject>— when records come from a custom source (in-memory list, computed set, external API result). Capped at 50,000 rows because the standard SOQL governor applies.
public Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator(
'SELECT Id, Status, AccountId FROM Case WHERE Status = \'New\' AND CreatedDate < LAST_N_DAYS:30'
);
}
2. execute — process each chunk
Runs once per chunk of records (default 200, configurable 1–2,000 via the scope parameter of Database.executeBatch). Each call is its own transaction with its own governor limits.
public void execute(Database.BatchableContext ctx, List<Case> scope) {
for (Case c : scope) {
c.Status = 'Auto-Closed';
c.Description = 'Closed by 30-day cleanup batch on ' + Date.today();
}
update scope;
}
If execute throws, that chunk fails but the rest of the job continues. Previously committed chunks are not rolled back. To make the entire job all-or-nothing, you have to fail every chunk — there’s no global rollback.
3. finish — wrap up
Runs once after all chunks complete (or after all chunks have either succeeded or failed). Use it for:
- Sending a completion email with job statistics
- Logging results to a custom object
- Kicking off the next batch in a chain (
Database.executeBatch(new NextBatch())) - Final cleanup of state
public void finish(Database.BatchableContext ctx) {
AsyncApexJob job = [
SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors, CreatedBy.Email
FROM AsyncApexJob WHERE Id = :ctx.getJobId()
];
Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
msg.setToAddresses(new String[] { job.CreatedBy.Email });
msg.setSubject('Case cleanup batch complete: ' + job.Status);
msg.setPlainTextBody(
'Processed: ' + job.JobItemsProcessed +
', Errors: ' + job.NumberOfErrors
);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { msg });
}
Side-by-side summary
| Method | Runs | Receives | Returns | Typical job |
|---|---|---|---|---|
start | Once | BatchableContext | QueryLocator or Iterable | Gather the dataset |
execute | Once per chunk | BatchableContext, List<sObject> | void | Process the chunk |
finish | Once | BatchableContext | void | Notify, chain, cleanup |
Common interview follow-ups
- What’s
BatchableContextfor? — It carries the JobId. Usectx.getJobId()to queryAsyncApexJobfor status. - Can
startreturn anIterableof custom objects? — Yes, viaIterable<sObject>, but you lose the QueryLocator advantage. Total records capped at 50,000. - Does
finishalways run? — Yes, even if every chunk failed. It’s the “the job is done” hook.
Verified against: Apex Developer Guide — Database.Batchable Interface. Last reviewed 2026-05-17.