Ask Krrish Contact Us
Home
Developer & Admin
Apex Triggers Mastery Asynchronous Apex SOQL & SOSL Governor Limits Flows & Process Builder
Advanced Topics
LWC Essentials Security & Sharing Managed Packages Deployment & CI/CD Integration Patterns
Interview Prep
Available Now
Debug Log Analyzer
Coming Soon
Org Comparator Soon
Resources About Ask Krrish Contact Us

Asynchronous Apex Interview Questions & Guide — SFX Support

Dive into the world of Asynchronous Apex, a critical skill for any Salesforce developer. This module will teach you how to overcome governor limits, handle long-running operations, and integrate with external systems efficiently.

Need hands-on help?

1. Introduction to Asynchronous Apex

Salesforce operates within a multi-tenant environment, meaning resources are shared among many customers. To ensure fair usage, Salesforce imposes strict governor limits on synchronous Apex transactions.

What is Asynchronous Apex?

Asynchronous Apex executes in the background, at a later time, in a separate thread. This allows the main transaction to complete without waiting. This is crucial for:

  • Bypassing Governor Limits: Async operations have higher limits (e.g., higher SOQL query limits, longer CPU time).
  • Long-Running Operations: Complex calculations, large data processing, or extensive callouts.
  • Callouts to External Systems: HTTP callouts must be made asynchronously.
  • Better User Experience: Prevents users from waiting for long processes to complete.

When to use Asynchronous Apex?

Consider asynchronous Apex when your business logic involves:

  • Making callouts to external web services.
  • Processing a large number of records (e.g., batch updates).
  • Performing complex calculations that require more CPU time.
  • Executing operations that don't need to happen immediately.
  • Chaining multiple operations together.

Salesforce provides several async options:

  • @future Methods
  • Queueable Apex
  • Batch Apex
  • Scheduled Apex

2. @future Methods

@future methods run Apex code in its own thread, at a later time. Particularly useful for making callouts to external web services and offloading long-running operations.

Key Characteristics:

  • Static Method: Must be a static method.
  • @future Annotation: Must be annotated with @future.
  • Void Return Type: Must return void.
  • Primitive Parameters: Parameters must be primitive data types. Cannot pass sObjects directly — pass IDs and query within the method.
  • Callouts: If making a callout, annotate with @future(callout=true).
  • No Chaining: One @future method cannot call another.
  • Limited Queue: Up to 50 @future calls per transaction.

Example: Making a Callout

Apex Code Example
public class ExternalServiceCallout {
    @future(callout=true)
    public static void sendAccountToExternalSystem(Set<Id> accountIds) {
        List<Account> accounts = [SELECT Id, Name, AnnualRevenue FROM Account WHERE Id IN :accountIds];
        System.debug('Sending ' + accounts.size() + ' accounts to external system asynchronously.');
        for (Account acc : accounts) {
            System.debug('Processing Account: ' + acc.Name + ' (' + acc.Id + ')');
            // HttpRequest req = new HttpRequest();
            // req.setEndpoint('http://api.external.com/accounts');
            // req.setMethod('POST');
            // req.setBody(JSON.serialize(acc));
            // Http http = new Http();
            // HTTPResponse res = http.send(req);
        }
    }
}

trigger AccountAfterInsert on Account (after insert) {
    Set<Id> newAccountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        newAccountIds.add(acc.Id);
    }
    if (!newAccountIds.isEmpty()) {
        ExternalServiceCallout.sendAccountToExternalSystem(newAccountIds);
    }
}

Limitations:

  • Cannot monitor progress directly.
  • No guaranteed execution order for multiple @future calls.
  • Cannot pass sObjects directly, requiring extra SOQL queries.

3. Queueable Apex

Queueable Apex provides a more flexible and robust way to run async Apex. Implements the Queueable interface and allows chaining jobs and passing sObjects.

Key Characteristics:

  • Queueable Interface: Class must implement Database.Queueable.
  • execute Method: Must contain a single execute method.
  • sObject Parameters: Can accept sObjects and collections as parameters.
  • Chaining: A Queueable job can enqueue another (up to 50 jobs in a chain).
  • Job ID: System.enqueueJob returns a job ID for monitoring.
  • Higher Limits: Generally higher limits than @future, especially heap size.

Example: Chaining Updates

Apex Code Example
public class AccountContactUpdater implements Queueable {
    private List<Account> accountsToProcess;
    private Boolean updateContacts;

    public AccountContactUpdater(List<Account> accounts, Boolean updateContactsFlag) {
        this.accountsToProcess = accounts;
        this.updateContacts = updateContactsFlag;
    }

    public void execute(QueueableContext context) {
        List<Contact> contactsToUpdate = new List<Contact>();
        for (Account acc : accountsToProcess) {
            acc.Description = 'Processed by Queueable Apex.';
            if (updateContacts) {
                for (Contact con : [SELECT Id, MailingCity FROM Contact WHERE AccountId = :acc.Id]) {
                    con.MailingCity = acc.BillingCity;
                    contactsToUpdate.add(con);
                }
            }
        }
        if (!contactsToUpdate.isEmpty()) {
            update contactsToUpdate;
        }
        // Chain another job if needed
        if (accountsToProcess.size() > 100) {
            System.enqueueJob(new AccountContactUpdater(
                accountsToProcess.subList(100, accountsToProcess.size()), updateContacts));
        }
    }
}

trigger AccountQueueableTrigger on Account (after insert, after update) {
    if (Trigger.isAfter) {
        List<Account> accountsToProcess = new List<Account>();
        for (Account acc : Trigger.new) {
            if (acc.AnnualRevenue != null && acc.AnnualRevenue > 1000000) {
                accountsToProcess.add(acc);
            }
        }
        if (!accountsToProcess.isEmpty()) {
            System.enqueueJob(new AccountContactUpdater(accountsToProcess, true));
        }
    }
}

4. Batch Apex

Batch Apex processes large numbers of records (up to 50 million) by breaking them into smaller chunks processed asynchronously.

Key Characteristics:

  • Database.Batchable Interface: Class must implement Database.Batchable<sObject>.
  • Three Methods:
    • start(): Called once. Returns a Database.QueryLocator or Iterable.
    • execute(): Called per batch chunk (up to 200 records by default). Main processing logic here.
    • finish(): Called once after all batches. Used for notifications or cleanup.
  • Higher Limits: Each execute call runs with its own set of governor limits.
  • Monitoring: Visible in Setup → Apex Jobs.
  • Queue: Up to 5 concurrent batch jobs.

Example: Mass Data Update

Apex Code Example
public class AccountRatingBatch implements Database.Batchable<sObject>, Database.AllowsCallouts {
    public String query;
    public String newRating;

    public AccountRatingBatch(String q, String rating) {
        this.query = q;
        this.newRating = rating;
    }

    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(query);
    }

    public void execute(Database.BatchableContext bc, List<Account> scope) {
        List<Account> accountsToUpdate = new List<Account>();
        for (Account acc : scope) {
            acc.Rating = newRating;
            accountsToUpdate.add(acc);
        }
        if (!accountsToUpdate.isEmpty()) {
            update accountsToUpdate;
        }
    }

    public void finish(Database.BatchableContext bc) {
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems
                            FROM AsyncApexJob WHERE Id = :bc.getJobId()];
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setToAddresses(new String[] {'admin@example.com'});
        mail.setSubject('Account Rating Batch Completed');
        mail.setPlainTextBody('Status: ' + job.Status + '. Processed: ' + job.JobItemsProcessed);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }
}

// Execute:
Database.executeBatch(new AccountRatingBatch(
    'SELECT Id, Name, Rating FROM Account WHERE CreatedDate = LAST_N_DAYS:30', 'Hot'), 200);

5. Scheduled Apex

Scheduled Apex executes Apex classes at specific times — ideal for daily cleanups, weekly reports, or monthly data syncs.

Key Characteristics:

  • Schedulable Interface: Class must implement Schedulable.
  • execute Method: Takes a SchedulableContext parameter.
  • Cron Expression: E.g., '0 0 1 * * ?' for 1 AM daily.
  • Single Instance: Only one instance of a class can be scheduled at a time.
  • Combine with Batch: Often used to schedule Batch jobs.

Example: Daily Data Cleanup

Apex Code Example
public class DailyDataCleanupScheduler implements Schedulable {
    public void execute(SchedulableContext sc) {
        List<Task> oldTasks = [SELECT Id FROM Task WHERE CreatedDate < LAST_N_DAYS:365 LIMIT 10000];
        if (!oldTasks.isEmpty()) {
            delete oldTasks;
        }
        // Kick off a Batch job for large-scale cleanup
        Database.executeBatch(new OldCaseCleanupBatch());
    }
}

// Schedule it:
System.schedule('Daily Cleanup Job', '0 0 0 * * ?', new DailyDataCleanupScheduler());

6. Choosing the Right Asynchronous Tool

Select the appropriate tool based on your requirements:

  • @future Methods: Simple fire-and-forget callouts or small background tasks. No sObject passing, no chaining.
  • Queueable Apex: Need to pass sObjects, chain jobs, or require more flexibility than @future. Returns a job ID for monitoring.
  • Batch Apex: Processing thousands to millions of records. Separate governor limits per chunk, built-in monitoring.
  • Scheduled Apex: Run a class at a predetermined time or recurring schedule. Often used to kick off a Batch or Queueable job.

General Rule of Thumb:

  1. Start with declarative automation (Flows, Process Builder).
  2. For simple fire-and-forget callouts → @future.
  3. If you need sObjects or chaining → Queueable Apex.
  4. For large data volumes → Batch Apex.
  5. For recurring tasks → Scheduled Apex (often kicking off Batch or Queueable).

7. Monitoring & Debugging Asynchronous Apex

Monitoring and debugging async jobs is crucial for ensuring background processes run correctly.

Monitoring Asynchronous Jobs:

  • Setup → Apex Jobs: Lists all async jobs with status, submission date, errors.
  • Setup → Scheduled Jobs: Shows all currently scheduled jobs.
  • AsyncApexJob Object: Query programmatically to get job info.
Apex Code Example
List<AsyncApexJob> jobs = [
    SELECT Id, JobType, Status, ApexClass.Name, JobItemsProcessed, TotalJobItems, NumberOfErrors
    FROM AsyncApexJob
    WHERE JobType IN ('BatchApex', 'Queueable', 'Future')
    ORDER BY CreatedDate DESC LIMIT 10];

for (AsyncApexJob job : jobs) {
    System.debug('Job: ' + job.ApexClass.Name + ' | Status: ' + job.Status);
    if (job.NumberOfErrors > 0) {
        System.debug('Errors: ' + job.NumberOfErrors);
    }
}

Debugging Asynchronous Jobs:

  • Debug Logs: Set up logs for the user who initiated the async job. Use appropriate logging levels.
  • System.debug(): Use liberally to track execution flow and variable values.
  • Exception Handling: Implement try-catch blocks. Log exceptions to a custom error object.
  • Limits.get*() Methods: Monitor governor limit consumption, especially in Batch execute methods.
Apex Code Example
public class DebuggingBatchExample implements Database.Batchable<sObject> {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    public void execute(Database.BatchableContext bc, List<Account> scope) {
        System.debug('Heap: ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
        System.debug('Queries: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
        try {
            for (Account acc : scope) {
                if (acc.Name == 'Error Account') {
                    throw new MyCustomException('Error Account encountered!');
                }
                acc.Description = 'Processed';
            }
            update scope;
        } catch (Exception e) {
            System.debug(LoggingLevel.ERROR, 'Error: ' + e.getMessage() + ' line ' + e.getLineNumber());
        }
    }

    public void finish(Database.BatchableContext bc) {
        System.debug('Batch Finished.');
    }

    public class MyCustomException extends Exception {}
}

8. Conclusion & Best Practices

Asynchronous Apex is indispensable for building robust, scalable Salesforce applications that handle complex requirements without hitting governor limits.

Key Takeaways:

  • Governor Limits: Async Apex helps you work around stricter synchronous limits.
  • Callouts: HTTP callouts must be made asynchronously.
  • Tool Selection: Match the tool to the use case — @future, Queueable, Batch, or Scheduled.
  • Bulkification: Always avoid SOQL/DML inside loops.
  • Error Handling: Implement comprehensive try-catch and logging.
  • Monitoring: Regularly check Apex Jobs and query AsyncApexJob.

General Best Practices:

  • Keep it Lean: Focus async methods on their core task. Delegate complex logic to helper classes.
  • Idempotency: Design operations so running them multiple times with the same input produces the same result.
  • Test Thoroughly: Write unit tests covering positive, negative, and bulk scenarios.
  • Consider Platform Events: For complex, decoupled integrations, Platform Events are a powerful async option.
  • Right Tool for the Job: Evaluate requirements carefully before picking an async method.

By mastering these asynchronous patterns, you'll build more powerful and resilient Salesforce applications.

Get Expert Help

Independent community initiative. Not affiliated with Salesforce.com, Inc.