INTERVIEW PREP 2025

Top 50 Salesforce Apex Interview Questions & Answers

Prepare for your Salesforce Platform Developer I & II interviews with real questions covering Apex fundamentals, triggers, SOQL, async patterns, and best practices.

📋 50 Questions 🎯 6 Topic Areas ⚡ PD1 & PD2 Coverage
⚡ Apex Fundamentals
1What is Salesforce Apex?+

Apex is a strongly typed, object-oriented programming language developed by Salesforce that runs on the Force.com platform. It lets developers execute flow and transaction control statements in conjunction with calls to the Force.com API. Apex syntax is similar to Java — it supports classes, interfaces, inheritance, generics, exception handling, and annotations. Key uses: Apex triggers, web services, batch processing, scheduled jobs, and custom controllers.

2What are governor limits and why do they exist?+

Governor limits are runtime limits enforced by the Salesforce platform to ensure no single tenant monopolises shared resources on the multi-tenant architecture. Key per-transaction limits:

// Per synchronous transaction
SOQL queries       : 100
DML statements     : 150
SOQL rows returned : 50,000
Heap size          : 6 MB (async: 12 MB)
CPU time           : 10,000 ms
HTTP callouts      : 100
Future calls       : 50
💡 Use Limits.getQueries() and Limits.getLimitQueries() at runtime to check remaining limits.
3What are the different data types in Apex?+

Apex supports primitive types, sObject types, collection types, and special types:

// Primitives
Integer   i = 42;
Long      l = 9999999999L;
Decimal   d = 3.14;
Double    db = 2.718;
Boolean   b = true;
String    s = 'Hello';
Date      dt = Date.today();
DateTime  dtt = DateTime.now();
Id        recordId = '001xx000003GYk1';
Blob      b64 = Blob.valueOf('data');

// Collections
List<Account>         accounts = new List<Account>();
Set<Id>               idSet    = new Set<Id>();
Map<Id, Account>      accMap   = new Map<Id, Account>();
4What is the difference between a class and an interface in Apex?+

A class is a blueprint for objects and can contain method implementations, fields, constructors, and inner classes. An interface defines a contract — only method signatures, no implementations. Classes implement interfaces using implements. Apex does not support multiple class inheritance but a class can implement multiple interfaces.

public interface Notifiable {
    void sendNotification(String message);
}

public class EmailNotifier implements Notifiable {
    public void sendNotification(String message) {
        // send email implementation
    }
}
5What is the difference between static and instance variables/methods?+

Static members belong to the class itself — they are shared across all instances and persist for the lifetime of the transaction. Instance members belong to a specific object instance. In Apex triggers, static variables are commonly used as recursive guards because they retain their value across trigger invocations within the same transaction.

public class TriggerHelper {
    // Static — shared across all calls in this transaction
    public static Boolean hasRun = false;

    // Instance — each new TriggerHelper() gets its own
    public Integer instanceCount = 0;
}
6What are access modifiers in Apex?+

Apex has four access modifiers: private (default for class members — accessible only within the class), protected (accessible within the class and its subclasses), public (accessible within the same namespace), global (accessible from any Apex code, including external packages). Classes themselves must be public or global to be visible outside the file.

7What is an sObject in Apex?+

An sObject (Salesforce Object) is any object that can be stored in the Salesforce database. It is the generic type for all Salesforce records. Standard sObjects include Account, Contact, Lead, Opportunity. Custom sObjects end in __c. You can use the generic SObject type or specific types like Account:

Account acc = new Account(Name = 'Acme', Industry = 'Technology');
SObject generic = acc; // generic reference
String name = (String) generic.get('Name'); // dynamic field access
8What is the difference between List, Set, and Map in Apex?+

List: ordered collection, allows duplicates, index-based access (list.get(0)). Set: unordered collection, no duplicates — useful for membership checks. Map: key-value pairs, keys are unique — commonly used to map IDs to records for efficient lookup instead of nested loops.

// Map for O(1) lookup instead of nested loops
Map<Id, Account> accMap = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);
for (Contact c : contacts) {
    Account parent = accMap.get(c.AccountId); // O(1)
}
9What is mixed DML and how do you handle it?+

Mixed DML error occurs when you perform DML on setup objects (User, Group, GroupMember, etc.) and non-setup objects in the same transaction. Solution: use @future to isolate one of the DML operations in a separate transaction.

public class UserHelper {
    @future
    public static void assignPermSet(Id userId, String psName) {
        PermissionSet ps = [SELECT Id FROM PermissionSet
                            WHERE Name = :psName LIMIT 1];
        insert new PermissionSetAssignment(
            AssigneeId = userId, PermissionSetId = ps.Id);
    }
}
10What is with sharing and without sharing?+

with sharing enforces the current user's sharing rules — SOQL queries and DML only act on records the user has access to. without sharing bypasses sharing rules and accesses all records. Default is without sharing if not specified. Best practice is to always explicitly declare sharing mode.

public with sharing class AccountService {
    // Respects user's sharing rules
    public List<Account> getUserAccounts() {
        return [SELECT Id, Name FROM Account];
    }
}
Advertisement
⚡ Apex Triggers
11What is an Apex Trigger?+

An Apex trigger is code that executes before or after specific database events on Salesforce records — insert, update, delete, and undelete. Triggers follow the syntax: trigger TriggerName on ObjectName (events) { body }. Best practice: one trigger per object that delegates to a handler class.

12What is the difference between before and after triggers?+

Before triggers fire before the record is written to the database. They are used to validate or modify field values on the records being inserted/updated — changes to Trigger.new records take effect automatically without an additional DML call. Record IDs are NOT yet available in before insert triggers.

After triggers fire after the record is saved. Record IDs are available. They are used to create/update related records (child records, lookup updates, cascade logic). You must use explicit DML for any changes.

13What are Trigger context variables?+
Trigger.new       // List of new records (insert, update, undelete)
Trigger.old       // List of old records (update, delete)
Trigger.newMap    // Map<Id, SObject> of new records
Trigger.oldMap    // Map<Id, SObject> of old records
Trigger.isInsert  // true on insert
Trigger.isUpdate  // true on update
Trigger.isDelete  // true on delete
Trigger.isUndelete// true on undelete
Trigger.isBefore  // true in before context
Trigger.isAfter   // true in after context
Trigger.size      // number of records in this batch (max 200)
Trigger.new is null in delete triggers. Trigger.old is null in insert triggers. Always check before accessing.
14What is bulkification and why is it important?+

Triggers can fire with up to 200 records in a single call (e.g., when a data loader imports 10,000 records, the trigger fires ~50 times with 200 records each). Placing SOQL or DML inside a loop multiplies those operations by the record count — 200 SOQL queries would immediately hit the 100-query governor limit.

// ❌ Wrong — SOQL inside loop
for (Account acc : Trigger.new) {
    List<Contact> contacts = [SELECT Id FROM Contact
                               WHERE AccountId = :acc.Id]; // 💣
}

// ✅ Correct — single SOQL outside loop
Set<Id> accountIds = Trigger.newMap.keySet();
Map<Id, List<Contact>> contactMap = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact
                   WHERE AccountId IN :accountIds]) {
    if (!contactMap.containsKey(c.AccountId))
        contactMap.put(c.AccountId, new List<Contact>());
    contactMap.get(c.AccountId).add(c);
}
15What is a recursive trigger and how do you prevent it?+

A recursive trigger is one that re-fires itself. For example: an after insert trigger updates the record, which fires the after update trigger, which updates again — an infinite loop. Prevention: use a static Boolean guard that is set to true on first execution and checked before proceeding.

public class TriggerHelper {
    public static Boolean hasRun = false;
}

trigger AccountTrigger on Account (after insert) {
    if (TriggerHelper.hasRun) return;
    TriggerHelper.hasRun = true;
    // trigger logic here
}
16What is the Trigger Handler pattern?+

The trigger handler pattern delegates all logic from the trigger to a separate class. The trigger itself is kept to 3–5 lines. Benefits: testable in isolation, easier to maintain, prevents multiple triggers competing on the same object.

trigger AccountTrigger on Account (before insert, after update) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    if (Trigger.isBefore && Trigger.isInsert) handler.onBeforeInsert(Trigger.new);
    if (Trigger.isAfter  && Trigger.isUpdate) handler.onAfterUpdate(Trigger.new, Trigger.oldMap);
}
17Can you have multiple triggers on the same object?+

Technically yes, but it is a best practice anti-pattern. The order of execution for multiple triggers on the same object is not guaranteed. This makes logic unpredictable and untestable. Best practice: one trigger per object, delegating to a handler class that organises all event-specific logic.

18What is addError() used for in triggers?+

addError() on a record or field prevents the DML operation and returns a user-friendly error message. It is used in before triggers to block invalid records from being saved.

trigger ContactValidation on Contact (before insert, before update) {
    for (Contact c : Trigger.new) {
        if (c.Email == null) {
            c.Email.addError('Email is required for all contacts.');
        }
    }
}
19What is the order of execution when a record is saved?+

Salesforce follows a specific order: 1. System validation → 2. Before triggers → 3. Custom validation rules → 4. Duplicate rules → 5. Record saved to database → 6. After triggers → 7. Assignment rules → 8. Auto-response rules → 9. Workflow rules → 10. Processes/Flows → 11. Escalation rules → 12. Entitlement rules → 13. Commit.

20Can you make HTTP callouts from a trigger?+

No — direct HTTP callouts from triggers are not allowed because callouts require a transaction to remain open while waiting for a response, which violates the synchronous trigger execution model. Use @future(callout=true) or Queueable Apex to make callouts asynchronously from a trigger.

Advertisement
🔍 SOQL & DML
21What is the difference between SOQL and SOSL?+

SOQL (Salesforce Object Query Language) queries a single object and its relationships — like SQL SELECT. SOSL (Salesforce Object Search Language) searches text fields across multiple objects simultaneously — like a search engine. Use SOQL when you know the object; use SOSL when searching across objects by keyword.

// SOQL — single object
[SELECT Id, Name FROM Account WHERE Industry = 'Technology']

// SOSL — across objects
[FIND 'Acme*' IN ALL FIELDS
 RETURNING Account(Id, Name), Contact(Id, Name)]
22What DML statements are available in Apex?+
insert  account;           // creates record
update  account;           // updates existing record (Id required)
upsert  account;           // insert if new, update if exists
delete  account;           // moves to Recycle Bin
undelete account;          // restores from Recycle Bin
merge   master duplicates; // merges up to 3 records

// Database methods — allow partial success
Database.SaveResult[] results = Database.insert(records, false);
23What is the difference between DML statements and Database methods?+

DML statements (insert list) fail the entire operation if any record fails — all-or-nothing. Database methods (Database.insert(list, false)) allow partial success — failed records are captured in SaveResult[] while successful records commit. Use Database methods when processing large batches where some failures are acceptable.

24What is WITH SECURITY_ENFORCED?+

WITH SECURITY_ENFORCED enforces field-level and object-level security on a SOQL query. If the running user does not have read access to any field or object in the query, a System.QueryException is thrown. It is best practice to include it in service classes and controllers to prevent unauthorised data access.

[SELECT Id, Name, AnnualRevenue FROM Account
 WITH SECURITY_ENFORCED
 WHERE Industry = 'Technology' LIMIT 200]
25What are relationship queries in SOQL?+

SOQL supports two types of relationship queries: Child-to-parent uses dot notation to traverse up the relationship. Parent-to-child uses a subquery with the child's relationship name (plural for standard, __r for custom).

// Child-to-parent (Contact → Account)
[SELECT Id, Name, Account.Name, Account.Industry FROM Contact]

// Parent-to-child (Account → Contacts)
[SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account]
26What is a SOQL for loop and when should you use it?+

A SOQL for loop processes query results in batches of 200 records at a time, avoiding heap size limits when querying large data sets. Use it when you expect more than a few thousand records.

// Standard — loads all records into heap at once
List<Account> accs = [SELECT Id FROM Account];

// SOQL for loop — streams in batches of 200
for (List<Account> batch : [SELECT Id FROM Account]) {
    // process batch — heap stays low
}
27What are aggregate functions in SOQL?+
COUNT()   // number of records
COUNT(fieldName) // non-null values in field
SUM(field)   // sum of numeric field
AVG(field)   // average
MIN(field)   // minimum
MAX(field)   // maximum

// Access via AggregateResult
for (AggregateResult ar : [
    SELECT Industry, COUNT(Id) cnt FROM Account
    GROUP BY Industry]) {
    System.debug(ar.get('Industry') + ': ' + ar.get('cnt'));
}
28What is dynamic SOQL and when should you use it?+

Dynamic SOQL builds query strings at runtime using Database.query(String). Use it when fields, objects, or conditions are unknown at compile time (e.g., user-configurable filters). Always sanitise string inputs with String.escapeSingleQuotes() to prevent SOQL injection.

String obj = 'Account';
String field = 'Industry';
String val = String.escapeSingleQuotes(userInput); // sanitise!
String q = 'SELECT Id, Name FROM ' + obj +
           ' WHERE ' + field + ' = \'' + val + '\'';
List<SObject> results = Database.query(q);
Advertisement
🔄 Asynchronous Apex
29What is @future and when do you use it?+

@future runs a static method asynchronously in a separate thread. Use cases: HTTP callouts from triggers (requires @future(callout=true)), mixed DML operations. Limitations: no return value, parameters must be primitive (no sObjects), cannot be called from another @future.

@future(callout=true)
public static void syncToExternal(List<Id> recordIds) {
    HttpRequest req = new HttpRequest();
    // make callout
}
30What is Queueable Apex and how does it differ from @future?+

Queueable Apex is a more powerful async pattern. Key differences: can accept non-primitive parameters (sObjects, custom classes), returns a job ID (AsyncApexJob), supports chaining (calling another Queueable from execute()), and can be monitored. Implement Queueable and call System.enqueueJob(new MyJob()).

public class ProcessAccountsJob implements Queueable {
    private List<Account> accounts;
    public ProcessAccountsJob(List<Account> accs) { accounts = accs; }
    public void execute(QueueableContext ctx) {
        // process accounts
        System.enqueueJob(new NextJob()); // chain
    }
}
31What is Batch Apex and when should you use it?+

Batch Apex processes millions of records in chunks. Governor limits reset per execute() batch. Use it when you need to process more records than a single synchronous transaction allows. Implement Database.Batchable<SObject> with start(), execute(), and finish() methods. Default batch size is 200; maximum is 2,000.

32What is Database.Stateful and when do you use it?+

By default, batch classes are stateless — instance variables reset between execute() calls. Implementing Database.Stateful allows instance variable values to persist across batches. Use it when you need to count totals, accumulate errors, or track cross-batch state.

global class SummaryBatch implements
    Database.Batchable<SObject>, Database.Stateful {
    global Integer totalProcessed = 0; // persists across batches
    global void execute(Database.BatchableContext bc, List<SObject> scope) {
        totalProcessed += scope.size();
    }
}
33What is Schedulable Apex?+

Schedulable Apex runs on a cron-based schedule. Implement the Schedulable interface and its execute(SchedulableContext ctx) method. Schedule with System.schedule('JobName', cronExpression, new MyJob()). Most commonly used to launch batch jobs on a schedule.

global class DailyCleanupScheduler implements Schedulable {
    global void execute(SchedulableContext ctx) {
        Database.executeBatch(new CleanupBatch(), 200);
    }
}
// Schedule: every day at 2am
System.schedule('Daily Cleanup', '0 0 2 * * ?', new DailyCleanupScheduler());
34What is the difference between synchronous and asynchronous Apex?+

Synchronous Apex executes in the current user thread — governor limits apply immediately and the user waits for completion. Asynchronous Apex (@future, Queueable, Batch, Schedulable) runs in a separate thread, often with higher limits (12 MB heap vs 6 MB, 200 SOQL queries vs 100). Use async when: making HTTP callouts from triggers, processing large data volumes, or performing mixed DML.

35How many Queueable jobs can be chained?+

In production, you can chain up to unlimited Queueable jobs — each execute() can enqueue one child. In test context, only one level of chaining is allowed (the enqueue call is accepted but the child job does not execute within the test unless you use additional Test.startTest()/stopTest() blocks). The Apex flex queue can hold up to 100 Queueable jobs.

36What is a Platform Event and how does it differ from a trigger?+

Platform Events are Salesforce's publish-subscribe messaging system. A publisher fires EventBus.publish(new My_Event__e(...)) and any subscriber trigger (trigger on My_Event__e (after insert)) receives it asynchronously. Unlike standard triggers, platform event triggers run in their own transaction and can be used for cross-org or cross-system integration.

Advertisement
🧪 Apex Test Classes
37What is the minimum code coverage required in Salesforce?+

Salesforce requires a minimum of 75% code coverage across all Apex classes and triggers before you can deploy to production. Individual classes/triggers do not have their own minimum — it is the overall average. Best practice: aim for 90%+ and ensure tests cover positive paths, negative paths, and edge cases — not just inflating coverage.

38What is Test.startTest() and Test.stopTest() used for?+

Test.startTest() resets all governor limit counters so the code under test has a fresh set. Test.stopTest() forces all async operations (@future, Queueable, Batch) to execute synchronously before the next assertion. Always place assertions after Test.stopTest() when testing async code.

@isTest static void testBatchJob() {
    insert testData;
    Test.startTest();
    Database.executeBatch(new MyBatchJob(), 200);
    Test.stopTest(); // batch runs synchronously here
    System.assertEquals(10, [SELECT COUNT() FROM Account].getSize());
}
39What is @TestSetup and when should you use it?+

@TestSetup marks a method that runs once before any test method in the class. The data it creates is rolled back and re-inserted fresh for each test method, ensuring test isolation. Use it to avoid duplicating test data creation across multiple test methods.

@isTest class AccountServiceTest {
    @TestSetup static void makeData() {
        insert new Account(Name = 'Test Account', Industry = 'Technology');
    }
    @isTest static void testMethod1() {
        Account a = [SELECT Id FROM Account LIMIT 1]; // fresh copy
    }
}
40How do you test HTTP callouts in Apex?+

Callouts cannot be made in test context. Create a mock class implementing HttpCalloutMock and register it with Test.setMock(HttpCalloutMock.class, new MyMock()) before the callout code runs.

@isTest
global class MockHttpResponse implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
        HTTPResponse res = new HTTPResponse();
        res.setStatusCode(200);
        res.setBody('{"status":"ok"}');
        return res;
    }
}

@isTest static void testCallout() {
    Test.setMock(HttpCalloutMock.class, new MockHttpResponse());
    Test.startTest();
    MyCalloutClass.doCallout();
    Test.stopTest();
}
41Can test classes access organisation data?+

By default, test classes run in isolation — they cannot see org data. The only exceptions are metadata (custom settings, custom metadata types) and data explicitly created in the test or @TestSetup. To access org data in tests, use @isTest(SeeAllData=true) — but this is strongly discouraged because it creates test dependencies on live data.

42What assertions are available in Apex test classes?+
System.assertEquals(expected, actual);
System.assertEquals(expected, actual, 'message'); // with message
System.assertNotEquals(expected, actual);
System.assert(condition);
System.assert(condition, 'failure message');
// API v57+ (more readable)
Assert.areEqual(expected, actual);
Assert.isTrue(condition, 'message');
Assert.isNotNull(value);
43How do you test that an exception is thrown?+
@isTest static void testNullInput() {
    try {
        MyService.processAccount(null);
        System.assert(false, 'Expected exception was not thrown');
    } catch (IllegalArgumentException e) {
        System.assert(e.getMessage().contains('required'));
    }
}
✅ Best Practices
44What are the key Apex best practices every developer should follow?+
  • Bulkify all code — never put SOQL or DML inside a loop
  • One trigger per object — delegate to a handler class
  • Use with sharing — always declare sharing mode explicitly
  • WITH SECURITY_ENFORCED — enforce FLS on SOQL queries
  • Use try-catch on DML — handle DmlException gracefully
  • No hardcoded IDs — use Custom Metadata or SOQL lookups
  • Avoid recursive triggers — use static guard variables
  • Write meaningful tests — assert outcomes, not just coverage
45What is the difference between null and empty in Apex?+

null means the variable has no value assigned. An empty string ('') or empty list (new List<Account>()) is a valid object with zero length. Calling methods on a null reference throws NullPointerException. Always null-check before accessing properties or methods on objects that could be null.

46How do you handle exceptions in Apex?+
try {
    update accounts;
} catch (DmlException e) {
    for (Integer i = 0; i < e.getNumDml(); i++) {
        System.debug(e.getDmlMessage(i));
    }
} catch (Exception e) {
    System.debug('Unexpected: ' + e.getMessage());
} finally {
    // always runs
}
47What is a custom exception in Apex?+

Custom exceptions extend the built-in Exception class. They allow you to throw meaningful, domain-specific errors. The class name must end in Exception.

public class InvalidInputException extends Exception {}

public static void processAmount(Decimal amount) {
    if (amount < 0) throw new InvalidInputException('Amount cannot be negative');
}
48What is the Service Layer pattern in Apex?+

The Service Layer pattern separates business logic from triggers and controllers into dedicated with sharing classes. Benefits: reusable across triggers, controllers, and batch jobs; testable in isolation; enforces consistent security. A service class contains static methods operating on collections of records.

public with sharing class AccountService {
    public static void updateAnnualRevenue(List<Account> accounts) {
        // bulk-safe business logic here
        update accounts;
    }
}
49How do you avoid hardcoded IDs in Apex?+

Hardcoded Salesforce IDs are org-specific and will fail in any other org (sandbox, production, scratch org). Alternatives: query by name (WHERE Name = 'My Queue'), use Custom Metadata Type records, use Custom Labels, or use Custom Settings. Custom Metadata is the preferred approach as it is deployable.

50What is the difference between savepoint and rollback?+

A savepoint marks a position in a transaction. Database.rollback(sp) undoes all DML performed after the savepoint was set. Useful for partial transaction rollback when a later operation fails and you need to undo earlier work cleanly.

Savepoint sp = Database.setSavepoint();
try {
    insert accountList;
    insert contactList; // if this fails...
} catch (DmlException e) {
    Database.rollback(sp); // ...accounts also rolled back
}