Salesforce Apex & Triggers Interview Guide (2026)
1. Explain the Salesforce Order of Execution with a Real-World Example.
Definition
The Salesforce Order of Execution (OOE) is the sequence of events Salesforce follows whenever a record is inserted, updated, deleted, or undeleted. It determines which automation runs first and which runs later.
Understanding the Order of Execution is one of the most frequently asked Salesforce Developer interview topics because multiple automations (Validation Rules, Flows, Apex Triggers, Assignment Rules, Roll-up Summaries, etc.) may execute on a single transaction.
How It Works
When a user clicks Save, Salesforce processes the record in a predefined sequence.
Simplified Order of Execution
User Saves Record
│
▼
System Validation
│
▼
Before Record-Triggered Flow
│
▼
Before Trigger
│
▼
Validation Rules
│
▼
Duplicate Rules
│
▼
Save Record (Not Committed Yet)
│
▼
After Trigger
│
▼
Assignment Rules
│
▼
Auto Response Rules
│
▼
Workflow Rules
│
▼
Workflow Field Updates
│
▼
(Triggers run again if Workflow updated fields)
│
▼
Processes / Process Builder (Legacy)
│
▼
After-save Flow
│
▼
Escalation Rules
│
▼
Roll-Up Summary Calculation
│
▼
Criteria-Based Sharing
│
▼
Commit Transaction
│
▼
Post Commit Logic
(Email, Future, Queueable, Platform Events)
Real-Time Example
Imagine an Opportunity is updated.
Company Requirement:
- Sales Rep changes Stage = Closed Won
- Validation Rule checks Amount > 0
- Before Trigger calculates Commission
- After Trigger creates Project
- Flow sends Welcome Email
- Future Method calls ERP API
Execution:
User Updates Opportunity
↓
Validation
↓
Before Flow
↓
Before Trigger
Calculate Commission
↓
Validation Rule
Amount > 0
↓
Record Saved
↓
After Trigger
Create Project
↓
After-save Flow
Send Email
↓
Commit
↓
Future Method
Call ERP API
Everything happens in this exact order.
Why Is It Important?
Suppose:
Flow updates a field.
Trigger also updates the same field.
Workflow updates another field.
You might accidentally execute the trigger twice, causing:
- Duplicate Tasks
- Duplicate Emails
- Duplicate Opportunities
- CPU Time Errors
This is why understanding the execution order helps prevent unexpected behavior.
Interview Scenario
Interviewer:
Why did my trigger execute twice?
Answer:
Because Workflow Field Updates (or certain automation) modify the record after the initial save, Salesforce performs another update within the same transaction, causing the before and after triggers to run again. That’s why recursion prevention is essential.
Best Practices
- Know the complete Order of Execution.
- Keep business logic in one automation tool when possible.
- Avoid mixing Flow, Workflow, Process Builder, and Apex for the same object.
- Use Trigger Handlers instead of writing logic directly in triggers.
- Prevent recursion.
Governor Limits / Performance
Poor understanding of the execution order can cause:
- Too many SOQL queries (101)
- Too many DML statements (151)
- CPU Time Limit Exceeded
- Duplicate automation
- Infinite trigger recursion
2. How Do You Prevent Recursion in Apex Triggers?
Definition
Recursion occurs when a trigger updates the same object again, causing itself to execute repeatedly until Salesforce stops the transaction.
This commonly happens when:
- Trigger updates the same record.
- Flow updates the record.
- Workflow updates fields.
- Process Builder performs another update.
Problem Example
Account Trigger
↓
Update Account
↓
Trigger Fires Again
↓
Update Again
↓
Trigger Fires Again
↓
CPU Time Exceeded
Solution 1: Static Boolean (Simple)
public class TriggerHelper {
public static Boolean isRunning = false;
}
Trigger
trigger AccountTrigger on Account(before update){
if(TriggerHelper.isRunning){
return;
}
TriggerHelper.isRunning = true;
//Business Logic
}
Problem with Static Boolean
Works only once.
Fails in Bulk Processing.
Suppose:
200 Records
↓
First 200 Processed
↓
Boolean = True
↓
Remaining Records Skipped
Not recommended.
Solution 2: Static Set (Recommended)
public class TriggerHelper{
public static Set<Id> processedIds = new Set<Id>();
}
Trigger
for(Account acc : Trigger.new){
if(!TriggerHelper.processedIds.contains(acc.Id)){
TriggerHelper.processedIds.add(acc.Id);
//Business Logic
}
}
Now every record executes only once.
Real-Time Example
Requirement:
Whenever Account Rating changes,
Create one Task.
Without recursion:
Update Account
↓
Task Created
↓
Task Trigger Updates Account
↓
Account Trigger Runs Again
↓
Another Task Created
↓
Infinite Loop
Using Static Set prevents duplicate processing.
Best Practices
- Prefer Static Set or Static Map over Static Boolean.
- Keep triggers idempotent (same input = same result).
- Avoid updating the same object inside its own trigger unless required.
- Use Trigger Handler Framework.
Governor Limits / Performance
Recursion often leads to:
- CPU Time Limit Exceeded
- Maximum Trigger Depth Exceeded
- Too Many SOQL Queries
- Duplicate DML Operations
3. How Do You Bulkify an Apex Trigger?
Definition
Bulkification means writing Apex code that processes multiple records efficiently in a single execution instead of assuming only one record is processed.
Salesforce can pass up to 200 records to a trigger in one transaction.
Bad Example (Not Bulkified)
trigger AccountTrigger on Account(after insert){
for(Account acc : Trigger.new){
Contact con = new Contact(
LastName='Test',
AccountId=acc.Id
);
insert con;
}
}
If 200 Accounts are inserted:
200 DML statements.
Governor Limit:
Maximum DML = 150
Transaction fails.
Good Example (Bulkified)
trigger AccountTrigger on Account(after insert){
List<Contact> contacts = new List<Contact>();
for(Account acc : Trigger.new){
contacts.add(new Contact(
LastName='Test',
AccountId=acc.Id
));
}
insert contacts;
}
Only:
1 DML
instead of
200 DML
Bulk SOQL Example
Bad
for(Account acc : Trigger.new){
Contact c = [
SELECT Id
FROM Contact
WHERE AccountId=:acc.Id
];
}
200 SOQL Queries.
Governor Limit:
Maximum SOQL = 100
Fails.
Good
Set<Id> accountIds = new Set<Id>();
for(Account acc : Trigger.new){
accountIds.add(acc.Id);
}
Map<Id,List<Contact>> accountContacts =
new Map<Id,List<Contact>>();
Then query all contacts in one SOQL and group them by AccountId.
Real-Time Example
Requirement:
When 500 Opportunities are inserted,
Create one Task per Opportunity.
Bulkified solution:
- Collect all Tasks.
- Insert once.
- No SOQL inside loops.
- No DML inside loops.
Best Practices
- Use Collections (List, Set, Map).
- Query once.
- DML once.
- Process all records together.
- Assume 200 records every time.
Governor Limits / Performance
Bulkification reduces:
- SOQL Count
- DML Count
- CPU Time
- Heap Size
It significantly improves scalability.
4. What Is a Trigger Handler Framework, and Why Is It Important?
Definition
A Trigger Handler Framework separates business logic from the trigger itself. The trigger becomes a lightweight entry point that delegates work to Apex classes.
Instead of putting all logic directly inside the trigger, you organize it into reusable handler methods.
Traditional Trigger (Not Recommended)
trigger AccountTrigger on Account(
before insert,
before update,
after insert,
after update
){
// Hundreds of lines of business logic
}
Problems:
- Difficult to read.
- Hard to test.
- Hard to maintain.
- Multiple developers editing the same trigger.
Trigger Handler Framework
Trigger
trigger AccountTrigger on Account(
before insert,
before update,
after insert,
after update
){
AccountTriggerHandler handler =
new AccountTriggerHandler();
if(Trigger.isBefore){
if(Trigger.isInsert){
handler.beforeInsert(Trigger.new);
}
if(Trigger.isUpdate){
handler.beforeUpdate(Trigger.new, Trigger.oldMap);
}
}
if(Trigger.isAfter){
if(Trigger.isInsert){
handler.afterInsert(Trigger.new);
}
if(Trigger.isUpdate){
handler.afterUpdate(Trigger.new, Trigger.oldMap);
}
}
}
Handler Class
public class AccountTriggerHandler{
public void beforeInsert(List<Account> newList){
//Business Logic
}
public void afterUpdate(List<Account> newList,
Map<Id,Account> oldMap){
//Business Logic
}
}
Real-Time Example
Company Requirements:
- Before Insert → Validate PAN.
- Before Update → Validate GST.
- After Insert → Create Welcome Task.
- After Update → Sync with ERP.
Each responsibility belongs in its own method, making the code easier to maintain.
Why Is It Important?
Benefits:
- One Trigger Per Object
- Separation of Concerns
- Reusable Logic
- Easier Unit Testing
- Better Team Collaboration
- Simplified Maintenance
Best Practices
- Maintain one trigger per object.
- Keep triggers logic-free.
- Use service/helper classes for complex business logic.
- Add recursion prevention in the handler layer.
- Follow consistent naming conventions.
Governor Limits / Performance
A Trigger Handler Framework does not directly increase limits but helps you write bulkified, optimized code that avoids excessive SOQL, DML, and CPU usage.
5. How Do You Avoid SOQL and DML Inside Loops?
Definition
Executing SOQL queries or DML operations inside loops is one of the most common causes of Governor Limit exceptions in Salesforce.
The solution is to use collections and perform operations in bulk.
Bad Example
for(Account acc : Trigger.new){
Contact con = [
SELECT Id
FROM Contact
WHERE AccountId=:acc.Id
];
}
If 200 Accounts are processed:
200 SOQL Queries
Governor Limit:
Maximum SOQL = 100
The transaction fails.
Correct Approach
Step 1: Collect IDs.
Set<Id> accountIds = new Set<Id>();
for(Account acc : Trigger.new){
accountIds.add(acc.Id);
}
Step 2: Query once.
List<Contact> contacts = [
SELECT Id, AccountId
FROM Contact
WHERE AccountId IN :accountIds
];
Step 3: Process in memory using Maps or grouped collections.
DML Example
Bad
for(Account acc : Trigger.new){
Task t = new Task(
Subject='Follow Up',
WhatId=acc.Id
);
insert t;
}
Good
List<Task> tasks = new List<Task>();
for(Account acc : Trigger.new){
tasks.add(new Task(
Subject='Follow Up',
WhatId=acc.Id
));
}
insert tasks;
Only one DML statement is used.
Real-Time Example
Requirement:
Whenever 200 Accounts are created:
- Check existing Contacts.
- Create one Task for each Account.
Optimized approach:
- Gather Account IDs.
- Query all Contacts in one SOQL.
- Build a
Map<Id, List<Contact>>. - Create all Tasks in a
List<Task>. - Perform a single
insert.
This approach is scalable and governor-limit friendly.
Best Practices
- Never write SOQL inside a
forloop. - Never perform DML inside a
forloop. - Use
Setto collect unique IDs. - Use
Mapfor fast lookups. - Batch inserts, updates, and deletes whenever possible.
- Always design code assuming up to 200 records per trigger execution.
Governor Limits / Performance
Key limits to remember:
| Governor Limit | Value |
|---|---|
| SOQL Queries | 100 (Synchronous) |
| DML Statements | 150 |
| Records Retrieved by SOQL | 50,000 |
| Records Processed in Trigger | Up to 200 per batch |
| CPU Time | 10 seconds (Synchronous) |
Following these practices results in:
- Faster execution
- Lower CPU consumption
- Better scalability
- Fewer governor-limit exceptions
6. How do you handle Mixed DML Operations?
Definition
Mixed DML occurs when you try to perform DML operations on Setup Objects and Non-Setup Objects in the same transaction.
Salesforce blocks this because Setup Objects affect user permissions and access, while Non-Setup Objects contain business data. Mixing them in one transaction could create inconsistent security states.
What are Setup Objects?
Examples:
- User
- UserRole
- Group
- GroupMember
- QueueSObject
- PermissionSetAssignment
- Territory
- Custom Settings (Hierarchy)
What are Non-Setup Objects?
Examples:
- Account
- Contact
- Opportunity
- Lead
- Case
- Custom Objects
Why does Salesforce throw Mixed DML?
Imagine this code:
Account acc = new Account(Name='ABC');
insert acc;
User u = new User(
LastName='John',
Alias='john',
Email='john@test.com',
Username='john@test.com',
TimeZoneSidKey='Asia/Kolkata',
LocaleSidKey='en_US',
EmailEncodingKey='UTF-8',
LanguageLocaleKey='en_US',
ProfileId=profileId
);
insert u;
Error:
MIXED_DML_OPERATION:
DML operation on setup object is not permitted
after you have updated a non-setup object.
How It Works
Salesforce separates transactions involving security metadata from transactions involving business records.
To resolve Mixed DML:
- Future Method
- Queueable Apex
- Platform Events
- Scheduled Apex
- Separate Transactions
Solution Using Future Method
public class UserService{
@future
public static void createUser(){
//Insert User
}
}
Trigger:
insert account;
UserService.createUser();
Now:
Transaction 1
Insert Account
↓
Commit
Transaction 2
Future Method
↓
Insert User
No Mixed DML.
Real-Time Example
Requirement:
When HR creates a new Employee record,
Automatically:
- Create Contact
- Create User
- Assign Permission Set
Wrong Approach
Insert Employee
↓
Insert Contact
↓
Insert User
↓
Assign Permission Set
↓
Mixed DML Error
Correct Approach
Insert Employee
↓
Insert Contact
↓
Commit
↓
Queueable Apex
↓
Create User
↓
Assign Permission Set
Best Practices
- Never insert User and Account together in one transaction.
- Use Queueable instead of Future for new development.
- Keep setup-related operations asynchronous.
- Separate business data and security-related operations.
Governor Limits / Performance
Future Methods:
- Maximum 50 future calls per transaction.
- No chaining.
Queueable:
- Better monitoring.
- Supports chaining.
- Preferred over Future.
Interview Follow-Up
Interviewer: Why does Queueable solve Mixed DML?
Answer:
Because Queueable runs in a separate transaction after the original transaction commits, so Setup and Non-Setup DML no longer occur together.
7. Future Method vs Queueable Apex vs Batch Apex
Definition
These are asynchronous Apex mechanisms used to execute code outside the current transaction.
Future Method
Purpose
Execute lightweight asynchronous tasks.
@future
public static void sendEmail(){
}
Features
- Runs asynchronously.
- Static methods only.
- Primitive parameters only.
- Cannot chain.
- Limited monitoring.
Real-Time Example
After Opportunity closes,
Send email notification.
No need to wait for the email before saving the Opportunity.
Queueable Apex
Purpose
Perform asynchronous work with complex objects and support chaining.
public class MyQueueable
implements Queueable{
public void execute(QueueableContext qc){
}
}
Execute:
System.enqueueJob(new MyQueueable());
Advantages
- Supports complex objects.
- Supports chaining.
- Job monitoring.
- Better debugging.
- Preferred over Future.
Real-Time Example
After Account creation:
- Create Customer in ERP.
- Generate PDF.
- Upload File.
- Call SAP API.
All handled asynchronously using Queueable.
Batch Apex
Purpose
Process millions of records.
global class BatchJob
implements Database.Batchable<SObject>{
}
Methods
start()
execute()
finish()
Salesforce processes records in chunks.
Example:
1,000,000 Accounts
↓
200
↓
200
↓
200
↓
Finish
Each batch gets fresh governor limits.
Comparison Table
| Feature | Future | Queueable | Batch Apex |
|---|---|---|---|
| Asynchronous | Yes | Yes | Yes |
| Complex Objects | No | Yes | Yes |
| Chaining | No | Yes | Yes (via finish) |
| Monitoring | Limited | Good | Excellent |
| Millions of Records | No | No | Yes |
| Governor Limits Reset | No | No | Yes (per batch) |
| Recommended | Legacy use | Most async tasks | Large data volumes |
Best Practices
Use:
Future
- Small async work.
- Existing legacy code.
Queueable
- API callouts.
- Complex processing.
- File generation.
- Preferred for new development.
Batch
- Data cleanup.
- Mass updates.
- Scheduled nightly jobs.
- Millions of records.
Governor Limits
Queueable:
- 50 enqueueJob calls per transaction.
Batch:
- Default batch size = 200.
- Fresh governor limits for every execute().
Interview Tip
If asked:
“Which one would you choose?”
Answer:
Queueable Apex for most asynchronous business logic because it supports complex objects, chaining, monitoring, and is the modern replacement for Future Methods.
8. When would you use Database methods instead of DML statements?
Definition
Salesforce provides two ways to perform DML:
Standard DML
insert accounts;
update accounts;
delete accounts;
Database Class
Database.insert(accounts,false);
Difference
Standard DML
If one record fails,
Entire transaction rolls back.
Database Methods
Can allow partial success.
Example
Insert 100 Accounts.
Record 45 has missing Name.
Using insert
100 Records
↓
1 Error
↓
Everything Fails
Using Database.insert
Database.SaveResult[] results =
Database.insert(accounts,false);
Result
99 Records Inserted
↓
1 Failed
SaveResult Example
for(Database.SaveResult sr : results){
if(sr.isSuccess()){
}else{
System.debug(sr.getErrors());
}
}
Real-Time Example
Importing 50,000 Leads.
Some rows have invalid Email.
Business Requirement:
Import valid Leads.
Skip invalid Leads.
Database methods are the correct choice.
Best Practices
Use Standard DML
- When all records must succeed.
Use Database Methods
- Data migration.
- Bulk imports.
- ETL jobs.
- Batch Apex.
- Integrations requiring partial success.
Governor Limits
Database methods count toward DML limits just like standard DML, but they give better control over error handling and partial processing.
Interview Follow-Up
Interviewer: What does false mean in Database.insert(records,false)?
Answer:
It sets allOrNone = false, allowing Salesforce to save valid records while returning errors only for invalid ones instead of rolling back the entire transaction.
9. Explain Governor Limits with Real Examples
Definition
Governor Limits are limits enforced by Salesforce to ensure that one customer’s code does not consume excessive shared resources in the multi-tenant platform.
Think of Salesforce as an apartment building where every tenant shares electricity and water. Governor Limits ensure that no single tenant uses all the resources.
Why Governor Limits Exist
Salesforce uses a multi-tenant architecture, where thousands of organizations share the same infrastructure. Limits protect platform stability and fairness.
Common Governor Limits
| Limit | Synchronous |
|---|---|
| SOQL Queries | 100 |
| DML Statements | 150 |
| Records Retrieved | 50,000 |
| CPU Time | 10 Seconds |
| Heap Size | 6 MB |
| Callouts | 100 |
Real Example 1
Bad Code
for(Account acc : Trigger.new){
Contact c=[
SELECT Id
FROM Contact
WHERE AccountId=:acc.Id
];
}
200 Accounts
↓
200 SOQL
↓
Limit = 100
↓
Exception
Too many SOQL queries:101
Real Example 2
for(Account acc : Trigger.new){
insert new Task();
}
200 Records
↓
200 Inserts
↓
Limit =150
↓
Too many DML statements:151
Real Example 3
Infinite recursion.
↓
Trigger executes repeatedly.
↓
CPU reaches 10 seconds.
↓
Apex CPU Time Limit Exceeded
Best Practices
- Bulkify code.
- Use Collections.
- Avoid SOQL in loops.
- Avoid DML in loops.
- Prevent recursion.
- Use asynchronous processing for long-running work.
- Cache repeated lookups where appropriate.
Performance Optimization
Instead of:
SOQL
↓
SOQL
↓
SOQL
↓
SOQL
Do:
One SOQL
↓
Map
↓
Lookup in Memory
Memory operations are much faster than repeated database queries.
Interview Tip
A strong answer explains not just the limits but how your coding practices avoid hitting them.
10. How do you optimize Apex code hitting CPU Time Limit?
Definition
The CPU Time Limit is the maximum processing time Salesforce allows for Apex execution in a single transaction.
Current synchronous limit:
10 seconds
If exceeded:
Apex CPU Time Limit Exceeded
Common Causes
- Nested loops.
- Infinite recursion.
- SOQL inside loops.
- DML inside loops.
- Repeated calculations.
- Large collections processed inefficiently.
- Too many automation tools firing in the same transaction.
Bad Example
for(Account acc : accounts){
for(Contact con : contacts){
if(acc.Id==con.AccountId){
}
}
}
If:
Accounts = 10,000
Contacts = 20,000
Iterations
10,000 × 20,000
=
200 Million Comparisons
This is highly inefficient.
Optimized Example
Map<Id,List<Contact>> accountContacts =
new Map<Id,List<Contact>>();
Now
Account
↓
Map Lookup
↓
O(1)
instead of scanning the entire Contact list for every Account.
Additional Optimization Techniques
Use Collections
Replace nested loops with Map, Set, or grouped lists for constant-time lookups.
Reduce Database Calls
- Query only required fields.
- Perform one bulk query instead of many.
- Move expensive work to asynchronous Apex if it doesn’t need to finish immediately.
Prevent Recursion
Use static sets/maps or handler frameworks to avoid unnecessary trigger re-entry.
Simplify Logic
Break large methods into reusable helper methods and eliminate duplicate calculations.
Real-Time Example
Requirement:
Updating 50,000 Opportunities recalculates Territory and Revenue.
Initial implementation:
- Nested loops
- Multiple queries
- Trigger recursion
Result:
CPU Time Limit Exceeded
Optimized solution:
- Single SOQL query.
- Maps for lookups.
- Trigger Handler Framework.
- Queueable Apex for ERP sync.
- Batch Apex for large-volume recalculation.
The transaction completed successfully within governor limits.
Best Practices
- Replace nested loops with Maps whenever possible.
- Use Trigger Handler Frameworks.
- Bulkify all logic.
- Process non-critical work asynchronously.
- Profile and refactor expensive algorithms.
- Keep trigger logic focused and lightweight.
Governor Limits / Performance
Key CPU-related limits:
| Metric | Limit |
|---|---|
| CPU Time (Synchronous) | 10 seconds |
| CPU Time (Asynchronous) | 60 seconds |
Optimizing CPU usage improves:
- Faster transaction completion.
- Better scalability.
- Reduced governor-limit failures.
- Improved user experience.
11. How do you process millions of records using Batch Apex?
Definition
Batch Apex is an asynchronous Apex feature that processes large volumes of records (thousands to millions) by dividing them into smaller chunks (batches). Each batch executes as a separate transaction with its own governor limits.
Use Batch Apex when:
- Updating millions of records
- Data migration
- Scheduled data cleanup
- Nightly jobs
- Large data synchronization
How It Works
Batch Apex implements the Database.Batchable interface and has three methods:
global class AccountBatch implements Database.Batchable<SObject> {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, Name FROM Account'
);
}
global void execute(Database.BatchableContext bc, List<Account> scope) {
for(Account acc : scope){
acc.Description = 'Processed';
}
update scope;
}
global void finish(Database.BatchableContext bc){
System.debug('Batch Completed');
}
}
Execute the batch:
Database.executeBatch(new AccountBatch(), 200);
Batch Execution Flow
1,000,000 Accounts
│
▼
Query Records (start)
│
▼
Batch 1 (200 Records)
│
▼
Batch 2 (200 Records)
│
▼
Batch 3 (200 Records)
│
▼
...
│
▼
finish()
Each batch executes independently.
Why is Batch Apex Powerful?
Every execution of execute() receives fresh governor limits.
Example:
Batch 1
SOQL = 100
CPU = 60 sec
↓
Batch 2
SOQL Reset
CPU Reset
↓
Batch 3
Again Reset
This makes Batch Apex suitable for processing millions of records.
Real-Time Example
Requirement
Every night:
- Update 3 million Opportunities.
- Recalculate Commission.
- Archive Closed Opportunities.
- Send summary email to Admin.
Solution:
- Scheduled Apex starts the Batch Job.
- Batch processes 200 records at a time.
finish()sends the completion email.
Batch Size
Default:
200 Records
Can be customized:
Database.executeBatch(new AccountBatch(),100);
Choose the batch size based on processing complexity.
QueryLocator vs Iterable
QueryLocator
Database.getQueryLocator()
Supports:
50 Million Records
Best for Salesforce object queries.
Iterable
Iterable<SObject>
Used when:
- Data comes from an API.
- Data is generated dynamically.
- Data isn’t retrieved via SOQL.
Stateful Batch
Normally:
Variables reset after each batch.
Using:
implements Database.Stateful
Variables retain values across batches.
Example:
global Integer totalUpdated = 0;
Each batch increments the count.
At the end:
Total Updated = 2,450,000
Best Practices
- Process only required fields.
- Keep execute() lightweight.
- Use QueryLocator for large SOQL queries.
- Use Database.Stateful only when necessary.
- Perform notifications in finish().
Governor Limits / Performance
Per execute():
| Limit | Value |
|---|---|
| SOQL Queries | 100 |
| DML Statements | 150 |
| CPU Time | 60 Seconds |
| Heap Size | 12 MB |
Fresh limits are applied to every batch.
Interview Follow-Up
Interviewer: Why does Batch Apex handle millions of records while Queueable cannot?
Answer:
Queueable runs as a single transaction, whereas Batch Apex splits work into multiple transactions. Each transaction gets fresh governor limits, making Batch Apex scalable for very large datasets.
12. Explain Savepoints and Rollbacks
Definition
A Savepoint marks a checkpoint within a transaction. If an error occurs later, you can rollback to that checkpoint instead of committing partial changes.
This helps maintain data consistency.
How It Works
Savepoint sp = Database.setSavepoint();
try{
insert account;
insert contact;
}catch(Exception e){
Database.rollback(sp);
}
Transaction Flow
Start Transaction
│
▼
Savepoint Created
│
▼
Insert Account
│
▼
Insert Contact
│
▼
Error Occurs
│
▼
Rollback
│
▼
Database Returns to Savepoint
No partial data remains.
Real-Time Example
Requirement:
Create:
- Account
- Contact
- Opportunity
If Opportunity creation fails,
Undo everything.
Solution:
Savepoint sp = Database.setSavepoint();
try{
insert acc;
insert con;
insert opp;
}catch(Exception e){
Database.rollback(sp);
}
Why Not Use Separate DML?
Without rollback:
Account Created
↓
Contact Created
↓
Opportunity Failed
↓
Database Inconsistent
Rollback ensures either all changes succeed or none are committed.
Best Practices
- Create savepoints only when needed.
- Roll back only on business-critical failures.
- Avoid unnecessary nested savepoints.
Governor Limits
Maximum Savepoints per transaction:
5
Interview Follow-Up
Interviewer: Does rollback undo emails or callouts?
Answer:
No. Rollback only reverses database changes within the transaction. External actions (such as emails already sent or completed callouts) are not undone.
13. What is Dynamic Apex?
Definition
Dynamic Apex allows your code to inspect metadata and manipulate objects at runtime instead of hardcoding object names and fields.
It enables generic, reusable solutions.
Static Apex
Account acc = new Account();
acc.Name='ABC';
Object is fixed.
Dynamic Apex
SObject obj = Schema.getGlobalDescribe()
.get('Account')
.newSObject();
obj.put('Name','ABC');
Object is determined at runtime.
Dynamic SOQL
String objectName='Account';
String query='SELECT Id,Name FROM '+objectName;
List<SObject> records = Database.query(query);
Dynamic Fields
String fieldName='Name';
String value=(String)obj.get(fieldName);
Real-Time Example
Requirement:
Build one utility that exports data from:
- Account
- Contact
- Lead
- Opportunity
Instead of writing four classes,
Create one generic Dynamic Apex utility.
Schema Describe
Map<String,Schema.SObjectType> objects =
Schema.getGlobalDescribe();
Retrieve fields:
Schema.SObjectType accountType =
Schema.getGlobalDescribe().get('Account');
Map<String,Schema.SObjectField> fields =
accountType.getDescribe().fields.getMap();
Best Practices
- Validate object and field names before using them.
- Avoid Dynamic SOQL when static SOQL is sufficient.
- Use bind variables whenever possible to prevent SOQL injection.
Governor Limits / Performance
Dynamic Apex is slightly slower than static Apex because Salesforce must evaluate metadata at runtime. Use it only where flexibility is required.
Interview Follow-Up
Interviewer: Where have you used Dynamic Apex in a project?
Answer:
I used Dynamic Apex to build a configurable data export utility that worked for multiple standard and custom objects without writing object-specific code.
14. How do you implement Custom Metadata in Apex?
Definition
Custom Metadata Types (CMDT) store configuration data that can be deployed between environments and accessed in Apex.
Unlike Custom Settings, metadata records are treated as metadata rather than business data.
Why Use Custom Metadata?
Instead of hardcoding values:
if(country == 'India'){
tax = 18;
}
Store:
| Country | Tax |
|---|---|
| India | 18 |
| USA | 8 |
| UK | 20 |
Admins can update configuration without modifying Apex code.
Apex Example
Tax_Config__mdt config =
Tax_Config__mdt.getInstance('India');
Decimal tax=config.Tax__c;
Real-Time Example
Requirement:
Discount percentage differs by country.
Instead of hardcoding:
India → 15%
USA → 10%
Germany → 20%
Store these values in Custom Metadata.
Whenever the business changes the percentage, the admin updates metadata instead of requesting a code deployment.
Benefits
- Deployable via Change Sets or DevOps tools.
- Cached by Salesforce for faster access.
- Eliminates hardcoded values.
- Easy for admins to maintain.
Best Practices
- Use Custom Metadata for business configuration.
- Use meaningful record names.
- Avoid hardcoding IDs or constants in Apex.
- Document metadata usage clearly.
Governor Limits / Performance
Custom Metadata is optimized by Salesforce and is generally faster than querying normal objects for configuration values. It also reduces maintenance effort.
Interview Follow-Up
Interviewer: When would you use Custom Metadata instead of Custom Settings?
Answer:
Use Custom Metadata for deployable application configuration shared across environments. Use Custom Settings when configuration is organization-specific or user/profile-specific and may change directly in production.
15. Explain the with sharing, without sharing, and inherited sharing keywords
Definition
These keywords determine whether Apex code enforces the organization’s record-level sharing rules.
They do not enforce Object-Level Security (OLS) or Field-Level Security (FLS); those must be handled separately.
1. with sharing
public with sharing class AccountService{
}
Respects:
- Organization-Wide Defaults (OWD)
- Role Hierarchy
- Sharing Rules
- Manual Sharing
User sees only records they are allowed to access.
Example
Sales User owns:
Account A
Cannot access:
Account B
Using:
with sharing
Query:
SELECT Id FROM Account
Returns only:
Account A
2. without sharing
public without sharing class AccountService{
}
Ignores record-level sharing.
The code can access all records permitted by object permissions.
Real-Time Example
Nightly integration:
ERP system must synchronize every Account regardless of ownership.
Use:
without sharing
to process all records.
3. inherited sharing
public inherited sharing class AccountService{
}
The class inherits the sharing behavior of the calling context.
If called from:
with sharing
it respects sharing.
If called from:
without sharing
it runs without sharing.
Comparison Table
| Keyword | Record-Level Sharing |
|---|---|
| with sharing | Enforced |
| without sharing | Ignored |
| inherited sharing | Inherits caller’s context |
Real-Time Scenario
Requirement:
Customer Portal users should see only their own Cases.
Solution:
public with sharing class CaseController{
}
Another requirement:
Nightly integration exports every Case to SAP.
Solution:
public without sharing class SAPSync{
}
Best Practices
- Default to
with sharingunless there is a justified reason not to. - Use
without sharingonly for trusted system processes or integrations. - Prefer
inherited sharingfor reusable service classes so behavior follows the caller’s security context. - Always enforce Object-Level Security (OLS) and Field-Level Security (FLS) separately using APIs such as
WITH USER_MODE,Security.stripInaccessible(), or appropriate access checks.
Governor Limits / Performance
Sharing keywords primarily affect security, not governor limits. However:
with sharingmay return fewer records, reducing processing time.without sharingcan return significantly more data, so ensure queries remain selective and bulkified.
16. How do you write Exception Handling in Apex?
Definition
Exception Handling is a mechanism used to catch and handle runtime errors gracefully so that the application does not fail unexpectedly.
Instead of allowing the transaction to terminate with an unhandled exception, Apex provides try, catch, and finally blocks to manage errors.
How It Works
try{
// Business Logic
}
catch(Exception e){
// Handle Error
}
finally{
// Always Executes
}
Execution Flow:
Start
│
▼
Try Block
│
├── No Error ───────────────► Finally ► End
│
└── Error Occurs
│
▼
Catch Block
│
▼
Finally Block
│
▼
End
Common Exception Types
| Exception | When It Occurs |
|---|---|
| DmlException | Insert/Update/Delete failure |
| QueryException | SOQL returns unexpected results |
| NullPointerException | Accessing a null object |
| ListException | Invalid list index |
| MathException | Division by zero |
| JSONException | Invalid JSON parsing |
| CalloutException | HTTP callout failure |
Example
try{
Account acc = new Account();
insert acc;
}catch(DmlException ex){
System.debug(ex.getMessage());
}
Multiple Catch Blocks
try{
// Logic
}catch(DmlException e){
}
catch(QueryException e){
}
catch(Exception e){
}
Always catch the most specific exceptions before the generic Exception.
Real-Time Example
Requirement:
When an Opportunity is Closed Won:
- Create Invoice
- Create Payment
- Call ERP API
If ERP API fails:
- Log the error
- Notify the Admin
- Retry asynchronously
- Prevent inconsistent business processing where appropriate
Logging Errors
Instead of only:
System.debug(e);
Create a custom logging solution.
Example:
Custom Object
Error Log
----------------
Class Name
Method
Stack Trace
Date
User
Record Id
This makes production troubleshooting much easier.
Best Practices
- Catch specific exceptions whenever possible.
- Never use an empty catch block.
- Log meaningful error details.
- Show user-friendly messages in UI contexts.
- Separate business logic from exception handling.
- Consider retry strategies for transient integration failures.
Governor Limits / Performance
Exception handling itself has minimal overhead. However:
- Avoid using exceptions for normal control flow.
- Excessive logging or repeated retries can impact CPU time and DML limits.
Interview Follow-Up
Interviewer:
Why shouldn’t we write:
catch(Exception e){}
Answer:
Because it silently hides errors, making debugging and production support extremely difficult. Every exception should either be handled appropriately, logged, or rethrown if necessary.
17. How do you test Asynchronous Apex?
Definition
Asynchronous Apex (Future, Queueable, Batch, Scheduled) runs after the current transaction.
In unit tests, asynchronous jobs do not execute automatically unless wrapped between:
Test.startTest();
Test.stopTest();
How It Works
Test.startTest();
System.enqueueJob(new MyQueueable());
Test.stopTest();
When stopTest() executes:
- Queueable runs.
- Future Methods run.
- Batch Apex executes.
- Scheduled Apex executes (if scheduled during the test).
Queueable Example
@IsTest
private class QueueTest{
@IsTest
static void testQueue(){
Test.startTest();
System.enqueueJob(new MyQueue());
Test.stopTest();
}
}
Future Method Test
Test.startTest();
MyFutureClass.sendEmail();
Test.stopTest();
Batch Test
Test.startTest();
Database.executeBatch(new AccountBatch(),200);
Test.stopTest();
Scheduled Apex Test
String cron='0 0 12 * * ?';
Test.startTest();
System.schedule(
'MyJob',
cron,
new MyScheduler()
);
Test.stopTest();
Real-Time Example
Requirement:
When an Opportunity is Closed Won:
- Trigger calls Queueable.
- Queueable calls SAP API.
- Response updates Opportunity.
Testing Steps:
- Insert test Opportunity.
- Update Stage to Closed Won.
- Call
Test.stopTest(). - Verify the Queueable updated the record as expected (mock external callouts where applicable).
Best Practices
- Always wrap async code with
Test.startTest()andTest.stopTest(). - Use
HttpCalloutMockfor testing callouts. - Assert business outcomes, not just execution.
- Create isolated test data.
Governor Limits
Test.startTest() resets governor limits, providing a fresh set of limits for the code under test.
Interview Tip
Many candidates know startTest() and stopTest(). Strong candidates also explain why they are needed and how they enable testing of asynchronous behavior.
18. How do you achieve 90%+ code coverage with meaningful test cases?
Definition
Salesforce requires at least:
75%
overall Apex code coverage to deploy to production.
However:
High code coverage does not guarantee high-quality tests.
A meaningful test verifies business behavior, edge cases, and error handling.
Good Testing Strategy
Cover:
- Positive scenarios
- Negative scenarios
- Bulk operations
- Exception handling
- Permission/security scenarios (where applicable)
- Null values
- Boundary conditions
Example
Trigger:
If Opportunity Amount > 100000
Create Approval Task.
Tests should verify:
✔ Amount = 200000
Task Created.
✔ Amount = 5000
Task Not Created.
✔ 200 Opportunities
Bulk Processing Works.
✔ Invalid Data
Proper error handling.
Test Data
Use:
@testSetup
Example:
@testSetup
static void setup(){
insert new Account(Name='ABC');
}
The same data is reused across test methods, improving readability and performance.
Assertions
Never write tests like:
System.assert(true);
Instead:
System.assertEquals(1,tasks.size());
Verify expected outcomes.
Real-Time Example
Requirement:
Trigger creates Contact automatically.
Test Cases:
- One Account
- 200 Accounts
- Missing Name
- Duplicate Data
- Existing Contact
- Exception Scenario
Best Practices
- Focus on business logic, not just line coverage.
- Use reusable test data.
- Keep tests independent.
- Use descriptive test method names.
- Verify both success and failure scenarios.
Governor Limits
Tests execute within governor limits, so bulk test cases also validate that your code remains governor-limit compliant.
Interview Follow-Up
Interviewer:
Is 100% coverage always better?
Answer:
Not necessarily. Well-designed tests with meaningful assertions are more valuable than simply achieving 100% coverage.
19. What is Platform Cache, and when would you use it?
Definition
Platform Cache is an in-memory storage feature that allows Apex to temporarily store frequently used data.
Instead of repeatedly querying the database, Apex retrieves the value directly from cache.
This reduces:
- SOQL Queries
- CPU Time
- Database Load
How It Works
Without Cache
Request
↓
SOQL
↓
Database
↓
Response
Every request hits the database.
With Cache
Request
↓
Cache
↓
Value Found
↓
Return Immediately
Database access is skipped.
Cache Types
Session Cache
Data is available only during the user’s session.
Examples:
- Shopping cart
- Wizard progress
- Temporary preferences
Organization Cache
Shared across users.
Examples:
- Tax rates
- Exchange rates
- Country codes
- Product configuration
Apex Example
Store:
Cache.Org.put('TaxRate',18);
Retrieve:
Integer tax=(Integer)Cache.Org.get('TaxRate');
Real-Time Example
Requirement:
Every Opportunity calculation needs the GST percentage.
Without Cache:
1000 Opportunities
↓
1000 SOQL Queries
With Cache:
Load Once
↓
Platform Cache
↓
Reuse Value
Performance improves significantly.
Best Practices
- Cache read-heavy, infrequently changing data.
- Do not cache sensitive or rapidly changing information.
- Handle cache misses by reloading the value from the source.
- Use appropriate expiration policies.
Governor Limits / Performance
Platform Cache helps reduce:
- SOQL consumption
- CPU time
- Database load
Remember that cached data is temporary and may expire or be evicted, so your code should always handle cache misses gracefully.
Interview Follow-Up
Interviewer:
Should customer balances be stored in Platform Cache?
Answer:
Generally, no. Financial balances change frequently and require strong consistency. Platform Cache is better suited for relatively stable reference or configuration data.
20. Explain a production issue you resolved using Apex
This is a behavioral interview question. Interviewers want to understand how you diagnose problems, communicate, and deliver reliable fixes.
A structured STAR (Situation, Task, Action, Result) answer works well.
Production Scenario 1 (CPU Time Limit)
Situation
Users reported that updating an Opportunity with many related records failed.
Error:
Apex CPU Time Limit Exceeded
Investigation
I reviewed:
- Debug Logs
- Apex Execution Overview
- Trigger Execution
- Flow Execution
I found:
- Nested loops.
- Duplicate SOQL queries.
- Trigger recursion.
Action
I:
- Replaced nested loops with
Map<Id, SObject>. - Bulkified the trigger.
- Added recursion prevention.
- Moved ERP synchronization to Queueable Apex.
- Removed redundant queries.
Result
- CPU time reduced significantly.
- No more governor-limit exceptions.
- Faster record saves.
- Improved user experience.
Production Scenario 2 (Duplicate Tasks)
Situation
Every Account update created multiple duplicate Tasks.
Root Cause
Workflow Field Updates caused the trigger to execute again within the same transaction.
Solution
Implemented:
- Trigger Handler Framework.
- Static Set-based recursion prevention.
- Idempotent business logic to avoid duplicate task creation.
Result
Duplicate Tasks were eliminated without affecting existing functionality.
Production Scenario 3 (Integration Failure)
Situation
ERP integration failed during peak business hours due to temporary network issues.
Root Cause
The trigger performed a synchronous callout, making record saves dependent on the external system.
Solution
- Moved integration to Queueable Apex.
- Added retry logic.
- Logged failures in a custom error object.
- Notified administrators for repeated failures.
Result
- Users could save records without waiting.
- Integration became more resilient.
- Failures were traceable and recoverable.
Best Practices for Answering Production Issue Questions
When answering in an interview:
- Clearly explain the business problem.
- Describe how you investigated it.
- Explain the technical solution.
- Quantify the outcome if possible.
- Mention preventive improvements implemented afterward.
Sample Interview Answer
In one project, users experienced Apex CPU Time Limit Exceeded while updating Opportunities. I analyzed debug logs and found nested loops, repeated SOQL queries, and trigger recursion. I refactored the code using Maps, bulkified the logic, implemented a Trigger Handler Framework with recursion prevention, and moved ERP synchronization to Queueable Apex. After deployment, transaction times improved significantly, governor-limit exceptions stopped, and users were able to save records reliably even during peak business hours.
📚 Best Salesforce Interview Preparation Resources (2026)
📘 Admin Q&A Pack — recruiter-asked, real answers
⚡ LWC Q&A Pack — with code & live scenarios
🔥 1000+ Real Salesforce Questions — asked in TCS, Infosys, EY, Wipro, Nagarro & more
💼 Mid-Senior (4–8 YOE) — curated Q&A set
💻 Data Engineer Interview Mega Pack — Azure | Databricks | SQL | ADF




