Skip to main content

Designing Sync and Async Apex: When Operations Must Run in Sequence

A fundamental Salesforce development decision is whether a given operation should run synchronously (within the current transaction) or asynchronously (in a separate transaction via Queueable, @future, or Batch). This choice affects data consistency, user experience, governor limits, and automation behavior. It is also a core PD1 exam topic.

Synchronous vs. Asynchronous Execution

Synchronous code runs inline with the user's action. The user waits for it to complete, and the entire operation succeeds or fails atomically.

Asynchronous code runs in a separate transaction after the current one commits. It has its own governor limits, its own DML context, and no guarantee of immediate execution.

CharacteristicSynchronousAsynchronous
User waitsYesNo
CPU limit10,000 ms60,000 ms (Queueable/Batch)
SOQL limit100 queries200 queries
DML limit150 statements150 statements
TransactionSame as triggering actionSeparate transaction
RollbackRolls back with parentIndependent
Data visibilitySees uncommitted changesOnly sees committed data

The Salesforce Order of Execution

When a record is saved, Salesforce follows a specific sequence:

  1. Load original record (or initialize for new)
  2. Load new field values from the request
  3. Execute before triggers
  4. Run system validation rules
  5. Run custom validation rules
  6. Execute duplicate rules
  7. Save record to database (not yet committed)
  8. Execute after triggers
  9. Execute assignment rules, auto-response rules
  10. Execute workflow rules
  11. Execute escalation rules
  12. Execute record-triggered Flows
  13. Execute entitlement rules
  14. Commit to database
  15. Execute post-commit logic (async jobs, platform events, outbound messages)

Asynchronous jobs enqueued during steps 3 or 8 execute after step 14 -- they only run once the transaction has fully committed.

When to Use Synchronous Processing

Use synchronous execution when:

  • The user needs immediate feedback. Field defaults, validation, error messages.
  • Data must be consistent within the transaction. A child record must reference its just-created parent.
  • Downstream automations depend on the result. A before trigger sets a field that a validation rule checks.
  • Rollback is required on failure. If step B fails, step A should also roll back.
// Synchronous: Set default values before save
trigger ItemTrigger on Item__c (before insert) {
for (Item__c item : Trigger.new) {
if (item.Priority__c == null) {
item.Priority__c = 'Medium';
}
}
}

When to Use Asynchronous Processing

Use asynchronous execution when:

  • The operation is expensive. Heavy calculations, many child records, external callouts.
  • The operation requires a committed parent record. You cannot query a record's related data until the transaction commits.
  • The operation touches a different object context. Updating an Opportunity based on its Quote requires a separate transaction to avoid mixed-DML or recursive trigger issues.
  • Failure should not block the user. A notification email failing should not prevent a record save.
// Asynchronous: Sync Quote totals to Opportunity after commit
public class QuoteSyncJob implements Queueable {
private Id quoteId;

public QuoteSyncJob(Id quoteId) {
this.quoteId = quoteId;
}

public void execute(QueueableContext ctx) {
Quote q = [SELECT Id, TotalPrice, OpportunityId FROM Quote WHERE Id = :quoteId];
Opportunity opp = [SELECT Id, Amount FROM Opportunity WHERE Id = :q.OpportunityId];
opp.Amount = q.TotalPrice;
update opp;
}
}

Decision Framework

Use this decision flow when determining sync vs. async:

Does the user need to see the result immediately?
YES -> Synchronous
NO -> Continue

Will the operation exceed CPU/SOQL limits if run inline?
YES -> Asynchronous
NO -> Continue

Does the operation depend on committed data from this transaction?
YES -> Asynchronous (must wait for commit)
NO -> Continue

Should failure of this operation block the entire save?
YES -> Synchronous
NO -> Asynchronous

Does the operation involve a callout to an external system?
YES -> Asynchronous (@future(callout=true) or Queueable)
NO -> Synchronous (if none of the above apply)

Common Pitfall: DML Before Async Enqueue

A Queueable enqueued during a trigger only executes after the transaction commits. If the transaction rolls back (due to a validation rule failure later in the order of execution), the Queueable is discarded -- it never runs. This is correct behavior, but developers sometimes expect the Queueable to execute regardless.

Choosing the Right Async Mechanism

MechanismBest ForChainingCallouts
@futureSimple, fire-and-forget operationsNoYes (with callout=true)
QueueableComplex logic, needs instance stateYes (depth 1 in triggers)Yes
Batch ApexProcessing thousands/millions of recordsVia finish()Yes
SchedulableTime-based recurring jobsVia Batch or QueueableNo (directly)

Understanding these trade-offs is essential for building scalable Salesforce applications and is heavily tested on the Platform Developer I exam.