Queueable Apex is a class that implements the Queueable interface and runs asynchronously when you submit it via System.enqueueJob(...). Salesforce introduced it in Winter ‘15 to address @future’s limitations — it accepts complex types, returns a JobId you can monitor, and can chain another job from inside its own execute method.
Anatomy
public class AccountSyncJob implements Queueable {
private List<Account> accounts;
public AccountSyncJob(List<Account> accs) {
this.accounts = accs;
}
public void execute(QueueableContext ctx) {
for (Account a : accounts) {
a.Last_Synced__c = Datetime.now();
}
update accounts;
}
}
Submit it:
Id jobId = System.enqueueJob(new AccountSyncJob(myAccounts));
System.enqueueJob returns the JobId synchronously. You can store it, query AsyncApexJob for status, or surface it to the user as a tracking reference.
Where Queueable beats @future
| Capability | @future | Queueable |
|---|---|---|
| Accept complex types (sObject, custom classes) | No | Yes |
| Return a JobId | No | Yes |
| Chain another async job | No | Yes — enqueueJob from inside execute |
| Track via Apex Jobs UI | Limited | Full |
| Make callouts | With annotation | With Database.AllowsCallouts interface |
In a 2026 codebase, Queueable is the default. @future exists for legacy code and a few one-line use cases.
Chaining
A Queueable can enqueue the next job from inside its own execute:
public class AccountSyncJob implements Queueable, Database.AllowsCallouts {
private List<Id> idsToProcess;
private Integer offset;
public AccountSyncJob(List<Id> ids, Integer offset) {
this.idsToProcess = ids;
this.offset = offset;
}
public void execute(QueueableContext ctx) {
Integer batchSize = 50;
Integer end = Math.min(offset + batchSize, idsToProcess.size());
List<Id> chunk = new List<Id>();
for (Integer i = offset; i < end; i++) chunk.add(idsToProcess[i]);
processChunk(chunk);
if (end < idsToProcess.size()) {
System.enqueueJob(new AccountSyncJob(idsToProcess, end));
}
}
private void processChunk(List<Id> chunk) { /* callouts here */ }
}
Each job processes a slice and queues the next. The chain ends when there’s no more work.
Limits to keep in mind
| Limit | Value |
|---|---|
| Queueable jobs enqueued per synchronous transaction | 50 |
Queueable jobs enqueued from inside a Queueable execute | 1 (you can only chain one child at a time) |
| Depth of Queueable chain | Effectively unlimited in production (sandboxes / Developer Edition cap at 5) |
Max enqueued + active Queueable jobs (org-wide, with Finalizers) | Counted toward the daily async limit (250,000) |
The “one chained job at a time” rule is the most-asked interview detail. From inside a Queueable, you can call System.enqueueJob once. Calling it twice throws LimitException: Too many queueable jobs added to the queue: 2.
Add callouts
public class StripeSyncJob implements Queueable, Database.AllowsCallouts {
public void execute(QueueableContext ctx) {
HttpResponse resp = new Http().send(/* ... */);
}
}
Database.AllowsCallouts is the Queueable equivalent of @future(callout=true).
Common interview follow-ups
- Which interface does Queueable implement? —
Queueable. One method:execute(QueueableContext ctx). - Can Queueable accept sObjects? — Yes, that’s a major win over
@future. - Can Queueable chain itself indefinitely? — In production, yes — there’s no hard depth cap (older docs said 5, but that limit applies only to Developer Edition / sandboxes).
- What does
System.enqueueJobreturn? — AId(the JobId of the newAsyncApexJob), useful for tracking.
Verified against: Apex Developer Guide — Queueable Apex. Last reviewed 2026-05-17 for Spring ‘26.