Asynchronous Apex

Future, Queueable, Batch and Schedulable Apex — when to use each, with examples and limits.

Developer 2 min readAsyncFutureQueueableBatchSchedulable

Async Apex runs later, in its own transaction, with higher limits (200 SOQL, 60 s CPU, 12 MB heap).

Comparison

@futureQueueableBatchSchedulable
StartCall the methodSystem.enqueueJob(job)Database.executeBatch(job, size)System.schedule(name, cron, job)
ParametersPrimitives / collections of primitives onlyAny (sObjects, objects)Query or Iterable—
Job Id / monitoring❌✅ AsyncApexJob✅✅ CronTrigger
Chaining❌✅ enqueue from execute✅ start another in finishLaunches other jobs
Best forSimple fire-and-forget, callouts from triggersMost async workMillions of recordsTime-based runs

@future

apex
public class GeoService {
    @future(callout=true)
    public static void geocode(Set<Id> accountIds) {
        // callout + update; can't take sObjects as parameters
    }
}

Queueable

apex
public class SyncContactsJob implements Queueable, Database.AllowsCallouts {
    private List<Contact> contacts;
    public SyncContactsJob(List<Contact> contacts) { this.contacts = contacts; }

    public void execute(QueueableContext ctx) {
        // work with complex types, then optionally chain:
        // System.enqueueJob(new NextJob());
    }
}

Id jobId = System.enqueueJob(new SyncContactsJob(newContacts));

Batch Apex

apex
public class DeactivateStaleAccounts implements Database.Batchable<SObject>, Database.Stateful {
    private Integer processed = 0;

    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id FROM Account WHERE LastActivityDate < LAST_N_DAYS:365'
        );
    }
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        for (Account a : scope) a.Active__c = 'No';
        update scope;
        processed += scope.size();
    }
    public void finish(Database.BatchableContext bc) {
        System.debug('Deactivated ' + processed + ' accounts');
    }
}

Database.executeBatch(new DeactivateStaleAccounts(), 200);  // scope 1–2,000
  • QueryLocator can process up to 50 million records.
  • Each execute call is its own transaction with fresh limits.
  • Database.Stateful keeps instance variables between chunks.

Schedulable

apex
public class NightlyCleanup implements Schedulable {
    public void execute(SchedulableContext sc) {
        Database.executeBatch(new DeactivateStaleAccounts());
    }
}
// Every day at 2 AM:
System.schedule('Nightly cleanup', '0 0 2 * * ?', new NightlyCleanup());

Testing async code

apex
Test.startTest();
System.enqueueJob(new SyncContactsJob(cons));
Test.stopTest();   // async work runs synchronously here, then assert

Interview questions

  1. Why can't @future methods take sObjects? → The record may change before the method runs; pass Ids and re-query.
  2. Queueable vs Future? → Queueable: complex params, job Id, chaining.
  3. How many records can batch Apex process? → Up to 50 million with a QueryLocator.
  4. How do you test async Apex? → Wrap in Test.startTest() / Test.stopTest().