Salesforce Apex Interview Questions and Answers

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 insert and update, 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.

  • Insert statement: 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 a DmlException is thrown.
  • Database.insert() method: This method provides more granular control through its optional allOrNone boolean parameter.
    • If allOrNone is true (the default), it behaves exactly like the insert statement.
    • If allOrNone is false, it allows for partial success. If one record fails, the other valid records in the list are still inserted. The method returns a Database.SaveResult array, which you can iterate through to identify which records succeeded and which failed (and why).

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.

  • try block: Contains the code that might throw an exception (e.g., a DML operation that could fail, a callout, or dividing by zero).
  • catch block: This block executes only if an exception occurs in the try block. You can catch specific exception types (like DmlException or CalloutException) to handle them differently.
  • finally block: This optional block always executes after the try and catch blocks, 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:

  1. Making a callout to an external web service from a process that doesn’t normally allow it, like a trigger.
  2. 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 static and can only return void.
  • 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 @future methods (one cannot call another).
  • You cannot track the job’s progress with a Job ID.
  • You are limited to 50 @future calls 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:

  1. start(): Collects the records to be processed.
  2. execute(): Performs the processing logic on one chunk of data.
  3. 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.

FeatureBatch ApexQueueable Apex
PurposeProcessing millions of records in chunks.Handling complex, multi-step asynchronous jobs.
Use CaseNightly data cleanup, large data migrations.Chaining sequential jobs, passing sObjects.
Chaining JobsCannot chain.Can chain one job to another.
MonitoringMonitored 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:

  1. 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.
Salesforce Apex Interview Questions


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.

Salesforce Apex Interview Questions


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 betweenwith 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 a with sharing class calls it, it runs as with sharing. If a without sharing class calls it, it runs as without sharing. This is a best practice for utility classes to make them flexible and context-aware.

22. What is the transientkeyword 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 .

Author

  • Salesforce Hours

    Salesforcehour is a platform built on a simple idea: "The best way to grow is to learn together". We request seasoned professionals from across the globe to share their hard-won expertise, giving you the in-depth tutorials and practical insights needed to accelerate your journey. Our mission is to empower you to solve complex challenges and become an invaluable member of the Ohana.


Discover more from Salesforce Hours

Subscribe to get the latest posts sent to your email.

1 thought on “Salesforce Apex Interview Questions and Answers”

Leave a Reply

Discover more from Salesforce Hours

Subscribe now to keep reading and get access to the full archive.

Continue reading