20 Real-Time Salesforce Asynchronous Apex Interview Questions & Answers

1. You need to update a User record and a non-setup object in the same transaction, but you’re hitting a MIXED_DML_OPERATION error. How do you resolve this?

Answer: This is a classic mixed DML scenario — you cannot perform DML on setup objects (User, Profile, etc.) and non-setup objects in the same transaction. The solution is to move one of the DML operations to an asynchronous context, typically a Future or Queueable method.

Key Points:

  • Setup objects: User, Profile, Role, PermissionSet, etc.
  • Async methods run in separate transaction boundaries
  • Future methods are simplest for fire-and-forget scenarios
public class MixedDMLErrorDemo {
    
    public static void demoMethod() {
        Account acc = new Account(name = 'Mixed DML Error Account');
        insert acc; //Inserting Account(Non-Setup Object)    
        UserUtility.insertUser(); // calling future method to insert user
    } 
    
}
public class UserUtility {
    @future
    public static void insertUser() {
        Profile p = [SELECT Id FROM Profile WHERE Name='Chatter Free User'];
        User usr = new User(alias = 'Maytas', email='Ma****@********nt.com', 
                            emailencodingkey='UTF-8', lastname='p', 
                            languagelocalekey='en_US', 
                            localesidkey='en_US', profileid = p.Id,
                            timezonesidkey='America/Los_Angeles', 
                            username='Maytas@flutterant.com1234');
        
        insert usr; //Inserting User(Setup Object)
        
    }
} 
//Both Account & user will be inserted

2. What are the key limitations of Future methods that might lead you to choose Queueable instead?

Answer: Future methods have several critical limitations:

  1. Cannot pass SObjects — only primitive types, arrays, or collections of primitives
  2. No return values — methods must be void
  3. Cannot be chained — one future method cannot call another
  4. Cannot be called from Batch or another Future
  5. Cannot be monitored easily — no Job ID returned
  6. Maximum 50 future calls per transaction

When to choose Queueable instead: Whenever you need to pass complex objects, chain jobs, get a Job ID for monitoring, or need to call async from Batch context.

3. How would you work around the limitation that Future methods cannot accept SObject parameters?

Answer: There are two approaches:

Recommended: Pass the record ID and query the record inside the future method:

@future
public static void processAccount(Id accountId) {
    Account acc = [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];
    <em>// Process the account</em>
}

Alternative (not recommended): Serialize the SObject to JSON and deserialize inside the future method:

@future
public static void processAccount(String serializedAccount) {
    Account acc = (Account)JSON.deserialize(serializedAccount, Account.class);
}

The ID approach is preferred because it ensures you work with the latest record data, avoiding stale data issues if the record was modified between method invocation and execution.

4. Walk me through a scenario where you would chain Queueable jobs. How do you implement it?

Answer: A classic scenario is processing a large file upload: parse → enrich → notify after completion.

public class ParseQueueable implements Queueable {
    private List<Id> recordIds;
    
    public ParseQueueable(List<Id> recordIds) {
        this.recordIds = recordIds;
    }
    
    public void execute(QueueableContext context) {
        // Parse and process first chunk of 50 records
        List<Id> processedIds = parseRecords(recordIds);
        
        // Chain to enrichment job
        System.enqueueJob(new EnrichQueueable(processedIds));
    }
}

public class EnrichQueueable implements Queueable, Database.AllowsCallouts {
    private List<Id> recordIds;
    
    public EnrichQueueable(List<Id> recordIds) {
        this.recordIds = recordIds;
    }
    
    public void execute(QueueableContext context) {
        // Make callouts to enrich each record
        for (Id recordId : recordIds) {
            enrichWithExternalAPI(recordId);
        }
        
        // Chain to notification job
        System.enqueueJob(new NotifyQueueable(recordIds.size()));
    }
}

Limitations to note:

  • Maximum 50 chained jobs in production orgs (5 in Developer Edition)
  • Only one child job can be enqueued per execute() method

5. Can you call a Queueable class from a Future method? Why or why not?

Answer: No, you cannot call a Queueable class from a Future method. This is a Salesforce platform restriction — you cannot chain asynchronous jobs across different async types in this direction. Future methods run in a restricted async context that doesn’t allow enqueuing other async jobs. However, the reverse is allowed — you can call a Future method from a Queueable, though it’s generally not recommended due to monitoring limitations.

6. What is the difference between Stateful and Stateless batch jobs? Provide a scenario where you’d need Stateful.

Answer:

  • Stateless (default): Each execute() transaction is independent. Instance variables reset between batches. Better performance since no serialization overhead.
  • Stateful: Implements Database.Stateful interface. Instance variables retain values across execute() transactions through serialization.

When to use Stateful: When you need to aggregate information across all batches, such as:

  • Calculating total processed records
  • Accumulating error counts
  • Building a summary report for the finish() method

Example:

public class SummaryBatch implements Database.Batchable<sObject>, Database.Stateful {
    private Integer totalProcessed = 0;
    private Integer totalErrors = 0;
    
    public void execute(Database.BatchableContext bc, List<sObject> scope) {
        totalProcessed += scope.size();
        // Process records, increment totalErrors on failures
    }
    
    public void finish(Database.BatchableContext bc) {
        System.debug('Processed: ' + totalProcessed + ', Errors: ' + totalErrors);
    }
}

7. How do you handle partial success in a Batch Apex job where some records may fail validation?

Answer: Use Database.update(list, false) (or insert/upsert/delete) which allows partial success. This method returns Database.SaveResult[] that you can iterate to identify failed records and log errors.

Implementation:

public void execute(Database.BatchableContext bc, List<Contact> scope) {
    Database.SaveResult[] results = Database.update(scope, false);
    
    List<BatchErrorLog__c> errorLogs = new List<BatchErrorLog__c>();
    
    for (Integer i = 0; i < results.size(); i++) {
        if (!results[i].isSuccess()) {
            BatchErrorLog__c log = new BatchErrorLog__c(
                Record_Id__c = scope[i].Id,
                Error_Message__c = results[i].getErrors()[0].getMessage()
            );
            errorLogs.add(log);
        }
    }
    
    if (!errorLogs.isEmpty()) {
        insert errorLogs;
    }
}

8. What happens if a batch job fails in one execution context? How does the platform handle it?

Answer: When a batch job fails in an execute() method:

  1. Only that specific batch (chunk) fails — the transaction rolls back for that chunk
  2. All previously successful batches remain committed — data changes persist
  3. The batch job continues — remaining batches are processed normally
  4. The status becomes “Completed” with errors — not “Failed”
  5. Apex Jobs UI shows the error — you can view details

Best practice: Implement error logging to track which records failed, and design the job to be idempotent so it can be re-run safely.

9. Can you call a Batch class from another Batch class? From a Future method?

Answer:

  • Batch from Batch: Yes, you can call Database.executeBatch() from the finish() method of another Batch class. This is a common pattern for sequential processing of different object types.
  • Batch from Future: No, you cannot call a Batch from a Future method. This is restricted by the platform to prevent unlimited async job spawning.
  • Batch from Queueable: Yes, you can call Database.executeBatch() from a Queueable’s execute() method.

10. What is the difference between Database.QueryLocator and Iterable in Batch Apex? When would you use each?

Answer:

  • Database.QueryLocator: Uses SOQL to retrieve records. Supports up to 50 million records. Use when source is a SOQL query and you need to leverage the platform’s query optimization.
  • Iterable: Allows custom logic to generate the record list. Use when data source is not a simple query (e.g., external system, complex calculations, or when passing records from another batch).

Iterable example scenario: You have a Batch that processes records, and you want to pass only successful records to a second Batch without re-querying. Use Iterable to pass the in-memory list.

11. You need a job to run every weekday at 2 AM to recalculate rollup summaries for 100,000+ records. How do you implement this?

Answer: I would use Scheduled Apex calling a Batch class. This is the classic pattern for recurring large-volume processing.
Implementation:

public class RollupScheduler implements Schedulable {
    public void execute(SchedulableContext sc) {
        Database.executeBatch(new RollupBatchClass(), 200);
    }
}

// Schedule via anonymous Apex:
String cronExp = '0 0 2 ? * MON-FRI *';
System.schedule('Daily Rollup', cronExp, new RollupScheduler());<br>

Why Schedule + Batch:

  • Schedulable interface defines timing
  • Batch handles large data volumes with governor limit safety
  • CRON expression provides flexible scheduling

12. What is the difference between System.scheduleBatch() and System.schedule()?

Answer:

  • System.scheduleBatch(): Specifically schedules a Batch Apex job to run at a future time (one-time execution). Returns a CronTrigger ID.
  • System.schedule(): Schedules any class implementing Schedulable interface. Can be recurring using CRON expressions. Returns a CronTrigger ID.

Usage:

// One-time batch execution after 5 minutes
System.scheduleBatch(new MyBatchClass(), 'One-time Job', 5);

// Recurring scheduled job
System.schedule('Daily Job', '0 0 2 * * ?', new MySchedulableClass());

13. Can Scheduled Apex make callouts? If not, what’s the workaround?

Answer: No, Scheduled Apex cannot directly make callouts. This is a platform restriction.

Workarounds:

  1. Schedule → Future with callout: Schedulable calls @future(callout=true) method
  2. Schedule → Queueable with callout: Schedulable enqueues a Queueable class implementing Database.AllowsCallouts
  3. Schedule → Batch with callout: Schedulable executes a Batch class implementing Database.AllowsCallouts

Example:

public class CalloutScheduler implements Schedulable {
    public void execute(SchedulableContext sc) {
        // Delegate callout to Queueable
        System.enqueueJob(new ExternalCalloutQueueable());
    }
}

14. What is the Apex Flex Queue? How many jobs can it hold?

Answer: The Apex Flex Queue is a holding area for batch jobs that are submitted but haven’t started processing yet. It allows you to:

  • View up to 100 queued batch jobs
  • Reorder jobs to prioritize critical ones
  • Monitor job status before execution

Limits:

  • Maximum 100 jobs in Holding status
  • Only 5 jobs can be in Queued or Processing status concurrently
  • Jobs stay in the flex queue until resources become available
// Submit batch to flex queue
Id jobId = Database.executeBatch(new MyBatchClass(), 200);

// Query flex queue
List<AsyncApexJob> queuedJobs = [SELECT Id, Status, CreatedDate 
                                 FROM AsyncApexJob 
                                 WHERE Status = 'Holding' 
                                 ORDER BY CreatedDate];

15. When testing a Batch Apex class, how many batches are executed?

Answer: In a test context, regardless of the number of records in your test data, only one batch of up to 200 records will be executed when Test.stopTest() is called. The finish() method will execute normally after that single batch. This is a test optimization to keep tests fast.

16. What is the purpose of the Database.RaisesPlatformEvents interface?

Answer: This interface, when implemented by a Batch Apex class, allows the platform to publish a BatchApexErrorEvent platform event automatically when the batch job encounters an error during processing. This enables developers to subscribe to these error events in a separate Apex trigger, Flow, or external process to build custom error handling and alerting systems without having to write complex logic within the batch’s execute or finish methods.

17. Describe a scenario where you would choose to use an Iterable instead of a QueryLocator in a Batch Apex start method.

Answer: You would use an Iterable when the source of the records to process is not a simple SOQL query. Common scenarios include:

Complex Logic: The set of records to process is generated by complex logic that cannot be easily expressed in a single SOQL query (e.g., a combination of records from different objects or the result of an external API call).

Post-Processing: You have a Batch job that processes records, and you want to pass only the successfully processed records to a second Batch job without re-querying the database. You can use an Iterable to pass this in-memory list of IDs or objects.

18. What is the difference between implementing Queueable and Queueable with Database.AllowsCallouts?

Answer: The plain Queueable interface does not permit HTTP or web service callouts by default. To make callouts from a Queueable job, the class must also implement Database.AllowsCalloutspublic class MyJob implements Queueable, Database.AllowsCallouts. Without this marker, any callout attempt throws a System.CalloutException at runtime.

19. A Trigger enqueues a Queueable job on every Account update. During a data Migration , 50000 accounts are updated via Data loader, causing the queue to flood. What is your solution?

Answer: This is a common production incident. The root cause is unbounded enqueueing without rate control. 

Immediate mitigation: – Pause the data migration, abort queued jobs via System.abortJob() where possible. 

Architectural fix: 

1. Use a Custom Metadata or Custom Setting flag to bypass the trigger during bulk operations: 
if (BatchBypassSetting__c.getInstance().Bypass_Account_Trigger__c) return; 
2. Instead of enqueueing one job per record, collect all affected Account IDs in the trigger and enqueue a single Queueable passing the full ID set. 3. If the ID set is very large, store the IDs in a custom staging object and let a Scheduled Job or single Batch process them in bulk.

// In trigger: collect IDs, enqueue once
if (!System.isFuture() && !System.isBatch()) {
  Set<Id> ids = Trigger.newMap.keySet();
  System.enqueueJob(new AccountSyncJob(ids));
}

20. A client wants real -time data sync between Salesforce and and external ERP. You cant use middleware. How do you architect this with async Apex ?

Answer:

Real-time” sync without middleware in Salesforce means event-driven, near-real-time (seconds latency, not milliseconds). 

Outbound (SF → ERP): 

1. Trigger on the relevant object fires on insert/update.

2. Trigger enqueues a Queueable with Database.AllowsCallouts (never call synchronously from trigger).

3. Queueable builds the payload and calls the ERP REST API.

4. On failure: implement retry with exponential backoff, log to Sync_Error__c

Inbound (ERP → SF):

1. Create a Salesforce Connected App + REST API endpoint (via Apex REST: @RestResource).

2. ERP posts changes to this endpoint.

3. The endpoint does minimal validation and publishes a Platform Event.

4. A Platform Event subscriber trigger processes the event asynchronously. 

Why not direct DML in the REST endpoint? 

Keeps the endpoint fast (returns 200 quickly), decouples processing from receipt, and gives you the Platform Event replay buffer for reliability. Conflict resolution: Use Last_Modified_Date__c comparison — if ERP timestamp < SF record timestamp, skip the inbound update (last-write-wins).

Author

  • Satyam parasa

    Satyam Parasa is a Salesforce and Mobile application developer. Passionate about learning new technologies, he is the founder of Flutterant.com, where he shares his knowledge and insights.

    View all posts

Leave a Comment