Preparing for a Salesforce Apex interview requires more than just knowing the syntax. It’s about proving you can write scalable, efficient code that respects the platform’s unique, multi-tenant architecture.
This guide from Salesforcehour breaks down the most common Apex interview questions for 2025. Inside, you’ll find:
1. What is Apex and how does it differ from Java?
Apex is a strongly typed, object-oriented programming language from Salesforce, designed for building business applications on the platform. While its syntax is very similar to Java, Apex has several key differences that are critical to understand.
- Hosted & Proprietary: Apex runs exclusively on the Salesforce platform. It is not a general-purpose language like Java that can be run on any server.
- Database-Centric: Apex is purpose-built for data-heavy operations. It has native support for DML (Data Manipulation Language) operations like
insertandupdate, and can directly embed SOQL (Salesforce Object Query Language) queries . - Governor Limits: Apex operates in a multi-tenant environment, meaning it shares resources with other companies. To ensure fairness, Salesforce enforces strict governor limits that control how many resources (like CPU time or database queries) a single transaction can consume.
2. What are Governor Limits in Apex?
As we know that Salesforce is a multi-tenant environment where resourses are shared . Governor limits are runtime limits enforced by the Apex runtime engine to ensure that any single transaction does not monopolize shared resources. They are the most critical constraint to be aware of when developing on the platform, as they prevent system slowdowns and ensure a stable environment for all customers.
It’s crucial to know the key synchronous limits, such as:
- Total number of SOQL queries issued: 100
- Total number of records retrieved by SOQL queries: 50,000
- Total number of DML statements issued: 150
- Total number of records processed as a result of DML statements: 10,000
- Maximum CPU time on the Salesforce servers: 10,000 milliseconds
Further Reading: For a detailed list, refer to the official documentation on Execution Governors and Limits.
3. What is the difference between “DATABASE.INSERT()” and ‘Insert’ in Apex?
Both are DML operations for creating records, but they offer different levels of flexibility for error handling in bulk operations.
Insertstatement: This is an “all-or-nothing” operation. If you try to insert a list of 100 records and even one record fails a validation rule, the entire transaction rolls back, none of the records are inserted, and aDmlExceptionis thrown.Database.insert()method: This method provides more granular control through its optionalallOrNoneboolean parameter.
- If
allOrNoneistrue(the default), it behaves exactly like theinsertstatement. - If
allOrNoneisfalse, it allows for partial success. If one record fails, the other valid records in the list are still inserted. The method returns aDatabase.SaveResultarray, which you can iterate through to identify which records succeeded and which failed (and why).
- If
Real-World Scenario
You need to import 1,000 new Lead records, but you suspect some may have invalid data. You want to insert as many as possible without letting a few bad records stop the entire process.
List<Account> accountList = new List<Account>();
// Populate accountList with 1000 records,Add some invalid...
// Use Database.insert with partial success enabled
Database.SaveResult[] saveResults = Database.insert(accountList, false);
// Iterate through the results to find failures
for (Database.SaveResult sr : saveResults) {
if (!sr.isSuccess()) {
// Operation failed, so get all errors
for(Database.Error err : sr.getErrors()) {
System.debug('The following error has occurred.');
System.debug(err.getStatusCode() + ': ' + err.getMessage());
System.debug('Account fields that affected this error: ' + err.getFields());
}
}
}
4. How do you handle exceptions in Apex?
You handle exceptions in Apex using a "try-catch-finally" block for robust error handling.
tryblock: Contains the code that might throw an exception (e.g., a DML operation that could fail, a callout, or dividing by zero).catchblock: This block executes only if an exception occurs in thetryblock. You can catch specific exception types (likeDmlExceptionorCalloutException) to handle them differently.finallyblock: This optional block always executes after thetryandcatchblocks, regardless of whether an exception occurred. It’s often used for cleanup operations.
Real-World Scenario
You’re developing an Apex method that makes a callout to an external payment gateway. If the gateway is down, you need to handle the CalloutException gracefully and log the error for investigation.
try {
// Attempt to make a callout to the payment gateway
HttpResponse response = makePaymentCallout(opportunityId);
// Process the successful response
} catch (System.CalloutException e) {
// The callout failed (e.g., endpoint down, timeout)
System.debug('Callout error: '+ e.getMessage());
// Create a custom Log record to track the failure
insert new Log__c(Details__c = 'Payment gateway callout failed for OppId: ' + opportunityId);
}
5. What is Bulkification, and why is it important in Apex?
Bulkification is the practice of writing Apex code to handle a collection of records at once, rather than processing them one by one. It is the single most important concept for writing scalable code and respecting governor limits on the Salesforce platform.
The goal of bulkification is to avoid hitting governer limits .
Real-World Scenario
You need to write a trigger that updates a custom field on all child Contacts whenever their parent Account’s address is updated.
Non-Bulkified Code (Incorrect):
// DANGER: SOQL and DML inside a loop. This will fail with more than a few accounts.
for (Account acc : Trigger.new) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // SOQL in loop
for (Contact con : contacts) {
con.Needs_Address_Update__c = true;
update con; // DML in loop
}
}
Bulkified Code (Correct):
// Get all Account Ids from the trigger context
Set<Id> accountIds = Trigger.newMap.keySet();
// Perform a single SOQL query to get all related contacts
List<Contact> contactsToUpdate = [SELECT Id FROM Contact WHERE AccountId IN :accountIds];
// Loop through the results in memory
for (Contact con : contactsToUpdate) {
con.Needs_Address_Update__c = true;
}
// Perform a single DML operation
if (!contactsToUpdate.isEmpty()) {
update contactsToUpdate;
}
6. What is Asynchronous Apex in Salesforce?
Asynchronous Apex is a framework that allows you to run processes in the background on a separate thread, at a later time. It is essential for long-running jobs, callouts from triggers, and any operation that might exceed synchronous governor limits. Asynchronous processes get their own, higher set of governor limits.
Real time Use case : A user clicks a button to kick off a complex calculation involving thousands of records. Instead of freezing the user’s screen, you can make the process asynchronous. The user gets an immediate “Process started” message and can continue working while the job runs in the background.
7. What is the difference between synchronous and asynchronous execution?
The primary difference is the timing and processing :
- Synchronous Execution: The code is executed sequentially and in real-time. The user or calling process must wait for the operation to complete before proceeding. Standard Apex triggers and Visualforce controllers are examples of synchronous execution.
- Asynchronous Execution: The operation is queued to be executed in the background when system resources become available. The user or calling process does not wait for it to complete and can move on to other tasks immediately.
8. What is the “@future” annotation in Apex?
The @future annotation is the simplest way to run an Apex method asynchronously. Marking a static method with @future tells the platform to run it in the background when resources are available.
It’s most commonly used for two purposes:
- Making a callout to an external web service from a process that doesn’t normally allow it, like a trigger.
- Offloading a simple, long-running operation to a separate thread.
Sytax of @future method :
public class MyFutureClass {
@future(callout=true)
public static void updateExternalSystem(Id accountId) {
// This code will run in the background
// Perform a callout to an external system here
}
}
9. What are the limitations of “@future” methods in Apex?
While simple, @future methods have significant limitations:
- The method must be
staticand can only returnvoid. - It can only accept primitive data types as parameters (e.g.,
Id,String) or collections of primitives. You cannot pass sObjects as parameters. - You cannot chain
@futuremethods (one cannot call another). - You cannot track the job’s progress with a Job ID.
- You are limited to 50
@futurecalls per Apex transaction.
For more detailed knowlege of future method and consideration : Click here
10. What is Batch Apex in Salesforce?
Batch Apex is the primary asynchronous tool for processing very large volumes of records (up to 50 million). It works by breaking a single large job into a series of smaller, manageable chunks ( called as batches), where each chunk is a separate transaction with its own governor limits.
A Batch Apex class implements the Database.Batchable interface, which has three required methods:
start(): Collects the records to be processed.execute(): Performs the processing logic on one chunk of data.finish(): Executes after all batches are processed for summary actions.
global class UpdateAccountIndustryBatch implements Database.Batchable<sObject> {
// The start method gets all accounts to be processed
global Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('SELECT Id, Name FROM Account WHERE Industry = NULL');
}
// The execute method runs for each batch of records
global void execute(Database.BatchableContext BC, List<Account> scope) {
for (Account acc : scope) {
acc.Industry = 'Technology';
}
update scope;
}
// The finish method runs once all batches are complete
global void finish(Database.BatchableContext BC) {
// Send an email or perform other summary actions
}
}
Further Reading: Dive deeper with the official guide on Using Batch Apex.
11. How does Queueable Apex differ from Batch Apex?
Both are powerful asynchronous tools, but they are designed for different use cases.
| Feature | Batch Apex | Queueable Apex |
|---|---|---|
| Purpose | Processing millions of records in chunks. | Handling complex, multi-step asynchronous jobs. |
| Use Case | Nightly data cleanup, large data migrations. | Chaining sequential jobs, passing sObjects. |
| Chaining Jobs | Cannot chain. | Can chain one job to another. |
| Monitoring | Monitored via AsyncApexJob. | Monitored via a returned Job ID. |
In short: Use Batch Apex for massive data volume. Use Queueable Apex for job complexity and chaining.
12. How do you chain Queueable jobs in Salesforce?
Chaining is a important feature of Queueable Apex that allows you to start a new job from an existing, running job, which is perfect for sequential operations. You achieve this by calling System.enqueueJob() from within the execute method of your Queueable class.
Real-World Scenario
You need to perform a series of dependent callouts. First get a customer ID, then use that ID to get their order history.
public class GetCustomerIdQueueable implements Queueable {
public void execute(QueueableContext context) {
// Callout to get Customer ID
Id customerId = ApiService.getCustomerId();
// Chain to the next job, passing the ID
System.enqueueJob(new GetOrderHistoryQueueable(customerId));
}
}
13. What is a Schedulable class in Salesforce?
A Schedulable class is an Apex class used to run a job at a specific, scheduled time. It is the ideal tool for daily, weekly, or monthly maintenance tasks, like data cleanup or report generation. To create one, your class must implement the Schedulable interface.
public class DailyCleanupScheduledJob implements Schedulable {
public void execute(SchedulableContext context) {
// Find and delete old log records
List<Log__c> oldLogs = [SELECT Id FROM Log__c WHERE CreatedDate < LAST_N_DAYS:30];
if (!oldLogs.isEmpty()) {
delete oldLogs;
}
}
}
14. How do you schedule an Apex class in Salesforce?
You can schedule a Schedulable class in two ways:
- Declaratively: From Setup, search for “Apex Classes,” in Quick Find then click the “Schedule Apex” button. This provides a UI to select your class and set the schedule.
2. Programmatically: Use the System.schedule method, which takes a job name, a CRON expression for the schedule, and an instance of your class.
// Cron expression for running at 2:00 AM every night
String cronExp = '0 0 2 * * ?';
String jobName = 'Daily Log Cleanup';
// Schedule the job
System.schedule(jobName, cronExp, new DailyCleanupScheduledJob());
15. How do you monitor asynchronous Apex jobs in Salesforce?
You can monitor all asynchronous jobs from the Apex Jobs page in Salesforce Setup. This page provides a list of all jobs, including their status (Queued, Processing, Completed, Failed), who submitted them, and progress details.

For more advanced monitoring, you can programmatically query the AsyncApexJob object.
SELECT Id, Status, JobItemsProcessed, TotalJobItems, CreatedBy.Name
FROM AsyncApexJob
WHERE JobType = 'BatchApex'
ORDER BY CreatedDate DESC
16. What happens if an error occurs in an asynchronous job?
If an unhandled exception occurs in an asynchronous job, the job’s status on the Apex Jobs page changes to “Failed.” The page displays the error message and stack trace for debugging. The platform will not automatically retry the job, so it’s a best practice to build your own error handling within your code.
17. What is the maximum number of records that a Batch Apex job can process?
A single Batch Apex job can process up to 50 million records in total, as returned by the QueryLocator in the start method. The execute method processes these records in smaller chunks (default size is 200) to stay within per-transaction governor limits.
19. How do you prevent a batch job from running when there’s no data to process?
The most robust pattern is to perform a count query before you call Database.executeBatch. If the count is zero, you simply don’t start the job. This prevents a job from entering the queue and consuming resources unnecessarily.
Integer recordCount = [SELECT count() FROM Account WHERE Status__c = 'Pending'];
if (recordCount > 0) {
Database.executeBatch(new MyBatchJob());
}
Attempting to handle this in the start method by returning null will cause an error. The check should happen before execution.
20. How does the “finish”method work in Batch Apex?
The finish method is a special method in a Database.Batchable class that is executed once, after all batches of records have been processed successfully. It is commonly used for post-processing and summary tasks.
Real-World Scenario
After a batch job finishes updating thousands of records, you want to send a single summary email to the system administrator detailing the outcome.
global void finish(Database.BatchableContext BC) {
// Get the ID of the AsyncApexJob record for this job
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob WHERE Id = :BC.getJobId()];
// Send an email to the user who started the job
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[] { job.CreatedBy.Email });
mail.setSubject('Batch Job Completed: Account Update');
mail.setPlainTextBody(
'The batch job has completed. \n' +
'Status: ' + job.Status + '\n' +
'Records Processed: ' + job.JobItemsProcessed + '\n' +
'Errors: ' + job.NumberOfErrors
);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
21. What is the difference between ‘with Sharing‘ ,’ without sharing ‘ , ‘inherited sharing‘ ?
These keywords are used in a class definition to control how Salesforce sharing rules are enforced for the records processed by that class. This is a fundamental security concept in Apex.
with sharing: This enforces the current user’s sharing rules. The code can only access records that the running user has permission to see based on OWDs, the role hierarchy, and sharing rules. This is the default for Aura and LWC controllers.without sharing: This bypasses sharing rules and gives the code access to all records, regardless of the running user’s permissions. It is often used for system-level operations or complex roll-up summary calculations where the logic needs to see all data to be accurate.inherited sharing: This makes the class inherit the sharing context from whatever class called it. If awith sharingclass calls it, it runs aswith sharing. If awithout sharingclass calls it, it runs aswithout sharing. This is a best practice for utility classes to make them flexible and context-aware.
22. What is the ‘transient‘ keyword and when would you use it?
The transient keyword is used to declare instance variables that should not be saved as part of the view state in Visualforce pages or when an Apex class is serialized.
Its primary purpose is to reduce the size of the view state, which can improve page performance and prevent the “Maximum view state size limit exceeded” error.
Real-World Scenario
You have a Visualforce page controller that queries a large, complex map of data. This map is used for some initial calculations when the page loads, but it is not needed for any re-renders or actions the user might take later. By declaring this map as transient, you prevent it from being included in the view state that is sent back and forth between the client and server, making the page much lighter and faster.
public class MyController {
public transient Map<Id, List<Custom_Object__c>> temporaryDataMap { get; set; }
public List<Account> accounts { get; set; }
public MyController() {
// This large map is only needed for the initial setup
temporaryDataMap = new Map<Id, List<Custom_Object__c>>([SELECT ...]);
// Use the map to build the final list of accounts
accounts = processData(temporaryDataMap);
}
}
23. What is a SOQL Injection and how do you prevent it?
SOQL Injection is a common security vulnerability where a malicious user can alter a SOQL query to gain unauthorized access to data. This happens when you build a query using simple string concatenation with user-provided input that has not been sanitized.
The best way to prevent it is to never use dynamic SOQL with untrusted user input.
- Best Practice (Static SOQL): Use static SOQL with bind variables (
:variableName). Salesforce automatically sanitizes bind variables, making this method immune to SOQL injection. - If Dynamic SOQL is Required: If you must build a query dynamically, always sanitize any user-provided input by using the
String.escapeSingleQuotes()method.
Real-World Scenario
You are building a custom search page where a user can type a contact’s name.
Vulnerable Code (Incorrect):
String searchText = ApexPages.currentPage().getParameters().get('name');
// DANGER: User input is directly concatenated into the query string.
String query = 'SELECT Id, Name FROM Contact WHERE Name LIKE \'%' + searchText + '%\'';
List<Contact> contacts = Database.query(query);
Secure Code (Correct):
String searchText = ApexPages.currentPage().getParameters().get('name');
searchText = '%' + searchText + '%';
// SAFE: The user input is treated as a bind variable, not part of the query logic.
List<Contact> contacts = [SELECT Id, Name FROM Contact WHERE Name LIKE :searchText];
Summary
Success in a Salesforce Apex interview comes down to two things:
1) A solid grasp of platform fundamentals like governor limits
2)The ability to articulate solutions to complex, real-world scenarios.
Do checkout our Interview preparation series if are preparing for Salesforce Interviews .
Discover more from Salesforce Hours
Subscribe to get the latest posts sent to your email.
1 thought on “Salesforce Apex Interview Questions and Answers”