Pages

Showing posts with label Apex Interview Questions. Show all posts
Showing posts with label Apex Interview Questions. Show all posts

Saturday, August 1, 2026

Apex Interview Questions — Part 5 (Design Patterns, Integration, Performance, Scenario-Based)

 

1. What is the Trigger Handler design pattern, and why is it considered a best practice at scale?

It centralizes all trigger logic into a single handler class per object, with the trigger itself only routing to context-specific methods (beforeInsert, afterUpdate, etc.). At scale, this avoids logic scattered across multiple triggers, makes unit testing easier since handler methods can be tested independently of a live trigger context, and gives one place to control execution order and recursion.

2. What is the Selector pattern in Apex, and what problem does it solve?

The Selector pattern centralizes all SOQL queries for a given object into a dedicated “selector” class, instead of scattering the same or similar queries across multiple classes. This avoids duplicated query logic, makes it easy to enforce field-level security and sharing consistently, and simplifies future query changes to a single place.

public class AccountSelector {
    public static List<Account> getActiveAccounts() {
        return [SELECT Id, Name FROM Account WHERE Active__c = true WITH SECURITY_ENFORCED];
    }
}

3. What is the Service Layer pattern in Apex?

A Service Layer class groups related business logic for a specific domain (e.g., OpportunityService) behind clear, well-named public methods, so that triggers, LWCs, and batch jobs all call the same central logic rather than duplicating business rules across multiple entry points.

4. What is dependency injection, and is it commonly used in Apex?

Dependency injection means providing a class with its dependencies (like a selector or service instance) from the outside rather than instantiating them internally, which makes unit testing easier through mocking. It’s less common in vanilla Apex than in languages like Java, but frameworks like fflib and Apex frameworks using the Application factory pattern implement it for more testable, decoupled code.

5. What is the Unit of Work pattern, and why is it useful in Apex?

The Unit of Work pattern batches multiple related DML operations (inserts, updates across different objects) into a single, coordinated commit, tracking the relationships between records so they’re all registered before one final “commit” call executes the DML in the correct dependency order. It reduces the risk of hitting DML statement limits and keeps complex multi-object operations transactionally consistent.

6. What is the difference between a REST callout and a SOAP callout in Apex?

A REST callout uses the Http, HttpRequest, and HttpResponse classes to send/receive typically JSON-based requests over standard HTTP verbs (GET, POST, PUT, DELETE). A SOAP callout uses a WSDL-generated Apex class (via Salesforce’s “Generate from WSDL” tool) to call a SOAP-based web service using strongly-typed method calls that mirror the WSDL’s operations.

7. How do you make a basic REST callout in Apex?

You construct an HttpRequest with an endpoint and method, send it via an Http instance, and inspect the returned HttpResponse:

HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/data');
req.setMethod('GET');
req.setHeader('Authorization', 'Bearer ' + accessToken);

Http http = new Http();
HttpResponse res = http.send(req);

if (res.getStatusCode() == 200) {
    Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
}

8. What is a Named Credential, and why is it preferred over hardcoding endpoint URLs and credentials?

A Named Credential stores an external system’s endpoint URL and authentication details securely at the org level, so Apex code references it by name instead of hardcoding sensitive values like API keys or passwords directly in code. This improves security, makes credentials easy to rotate without a code deployment, and centralizes authentication handling (including OAuth flows) outside of Apex logic.

9. What is a Remote Site Setting, and when is it required?

A Remote Site Setting whitelists an external domain that Apex is allowed to make callouts to; without registering the domain (or using a Named Credential, which handles this automatically), any callout to an unregistered endpoint throws an UnauthorizedEndpointException.

10. How do you handle a callout timeout gracefully in Apex?

You set a timeout explicitly on the HttpRequest (setTimeout(), in milliseconds, up to a max of 120,000ms) and wrap the callout in a try-catch to catch a CalloutException, allowing you to retry, log the failure, or notify the user instead of letting the transaction fail with an unhandled error.

req.setTimeout(10000);
try {
    HttpResponse res = http.send(req);
} catch (CalloutException e) {
    System.debug('Callout failed: ' + e.getMessage());
}

11. What causes a “Too many SOQL queries” error in a complex, multi-trigger scenario, and how do you debug it?

This typically happens when multiple triggers or workflow-driven updates on related objects cascade into each other within the same transaction, each independently issuing SOQL queries that accumulate against the shared 100-query limit. Debugging usually involves checking debug logs for the query count at various points (via the Limits class) and consolidating queries into selector methods called once and reused across the transaction.

12. A batch job is processing millions of records but keeps timing out or hitting heap limits — what would you check first?

First, check whether the execute() method is doing unnecessary work per batch, like re-querying related data inside a loop instead of bulk-querying it once per batch. Also check the batch size (Database.executeBatch()’s second parameter) — reducing it can lower heap usage per execution, and verify the start() method’s QueryLocator isn’t selecting more fields than needed.

13. How would you design a system where multiple integrations update the same Salesforce object without triggers overwriting each other’s changes?

A common approach is to centralize all incoming update logic through a single Apex service layer (rather than separate trigger paths per integration), using the trigger handler pattern to control execution order, along with careful use of Trigger.isUpdate checks and field-level comparisons to avoid unnecessary re-processing or overwriting fields set by a different integration in the same transaction.

14. What would you do if a trigger update in a Batch Apex job causes a “field is not writeable” security error for some users?

This usually indicates the batch is running in a system context without appropriate field-level security enforcement, or the running user in a scheduled context lacks edit access to a specific field. You’d check whether Security.stripInaccessible() or explicit FLS checks are needed, or whether the batch class should run under a more appropriate user context via System.runAs() in tests, and review the actual running user’s permission set in production.

15. How do you optimize a SOQL query that’s slow on a large object with millions of records?

Ensure the WHERE clause filters on indexed fields (standard indexed fields, external IDs, or custom fields marked for indexing via a support case), avoid LIKE '%value%' patterns which can’t use an index efficiently, use selective filters to keep the query below the “non-selective query” threshold, and consider using LIMIT combined with pagination if only a subset of records is actually needed.

16. What is the difference between a selective and non-selective SOQL query?

A selective query filters using indexed fields and returns a small percentage of the total records in the object, allowing Salesforce’s query optimizer to use an index efficiently. A non-selective query — often one filtering on unindexed fields or using leading wildcards — forces a full table scan internally, which becomes increasingly slow as the object’s record count grows into the millions.

17. How would you design Apex code to safely handle a scenario where an external system sends duplicate webhook callouts?

You’d implement idempotency — for example, by having the external system pass a unique transaction/request Id, storing recently processed Ids (in a custom object or platform cache), and checking for that Id before processing a new payload, so a duplicate callout doesn’t create duplicate records or apply the same update twice.

18. What is Platform Cache, and how might it help in a high-volume integration scenario?

Platform Cache stores frequently accessed data in a fast, temporary, org-wide or session-based cache, reducing repeated SOQL queries for data that doesn’t change often (like configuration settings or reference data). In a high-volume integration, it can reduce query counts and improve performance by avoiding redundant lookups within and across transactions.

19. In an interview, how would you answer “How do you handle governor limit exceptions gracefully in production”?

A strong answer covers proactive prevention (bulkifying code, using selectors, monitoring query/DML counts via the Limits class during development) combined with reactive handling — wrapping risky operations in try-catch where feasible, logging detailed context when a LimitException does occur (since it can’t always be caught cleanly), and using Batch Apex or Queueable chaining to break up large operations before they approach a limit in the first place.

20. A hiring manager asks you to describe a real production issue you solved involving Apex — what should a strong answer include?

A strong answer names the specific symptom (e.g., an intermittent UNABLE_TO_LOCK_ROW error or a SOQL 101 limit failure under bulk load), explains the root cause you identified (e.g., DML inside a loop, or two processes updating the same parent record concurrently), describes the concrete fix (bulkifying the trigger, adding FOR UPDATE locking, or restructuring the batch size), and mentions how you verified the fix held up under a realistic data volume test — not just that it passed a single test case.

Apex Interview Questions — Part 4 (Async Apex, Exception Handling, Sharing Rules)

 

1. What is asynchronous Apex, and why would you use it?

Asynchronous Apex runs in a separate thread from the user’s transaction, at a later time, rather than blocking the user’s immediate interaction. It’s used for long-running operations, callouts, bulk processing, or scheduled jobs — anything that shouldn’t hold up the user interface or that needs higher governor limits than synchronous execution allows.

2. What are the four main types of asynchronous Apex?

Future methods, Queueable Apex, Batch Apex, and Scheduled Apex. Each solves a different problem: Future methods handle simple fire-and-forget tasks, Queueable adds chaining and complex parameter support, Batch Apex processes very large data volumes in chunks, and Scheduled Apex runs code at a specific time or on a recurring basis.

3. What is a Future method, and what are its limitations?

A Future method is a static method annotated with @future that runs asynchronously. Its main limitations are that it can only accept primitive data types or collections of primitives as parameters (no sObjects), it can’t be chained to call another future method, and you can’t track its execution status directly.

public class AsyncService {
    @future
    public static void updateAccounts(Set<Id> accountIds) {
        List<Account> accs = [SELECT Id, Description FROM Account WHERE Id IN :accountIds];
        for (Account a : accs) {
            a.Description = 'Updated asynchronously';
        }
        update accs;
    }
}

4. What is Queueable Apex, and how is it different from a Future method?

Queueable Apex implements the Queueable interface and offers advantages Future methods lack: it accepts complex, non-primitive parameters (including sObjects), supports job chaining (one queueable job enqueuing another), and returns a job Id you can use to monitor status via AsyncApexJob.

public class UpdateAccountsQueueable implements Queueable {
    private List<Account> accounts;

    public UpdateAccountsQueueable(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void execute(QueueableContext context) {
        update accounts;
    }
}

5. How do you chain Queueable jobs together?

Inside the execute() method of one Queueable class, you call System.enqueueJob() with a new instance of the next Queueable class. Salesforce allows chaining, but be mindful of the chain depth limits enforced per context (5 in most contexts, unlimited in some Enterprise/Unlimited editions, though best practice is to avoid excessively long chains).

public void execute(QueueableContext context) {
    // do work
    System.enqueueJob(new NextStepQueueable());
}

6. What is Batch Apex, and when should you use it?

Batch Apex implements the Database.Batchable interface and processes large numbers of records (up to 50 million) by splitting them into smaller batches (default 200 records), each executed in its own governor-limit context. It’s the right choice when a Future or Queueable job would exceed limits due to sheer data volume.

public class UpdateAllAccountsBatch implements Database.Batchable<sObject> {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id, Description FROM Account');
    }
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        for (Account a : scope) {
            a.Description = 'Batch updated';
        }
        update scope;
    }
    public void finish(Database.BatchableContext bc) {
        // post-processing, e.g., send a summary email
    }
}

7. What are the three methods every Batch Apex class must implement?

start(), which defines the scope of records to process (via a QueryLocator or Iterable); execute(), which runs once per batch of records (called multiple times); and finish(), which runs once after all batches complete, often used for follow-up actions like sending a summary email or chaining another batch job.

8. How do you control the batch size in Batch Apex?

You pass a second parameter to Database.executeBatch() specifying the number of records per batch (default is 200, maximum is 2000):

Database.executeBatch(new UpdateAllAccountsBatch(), 100);

9. What is Scheduled Apex, and how do you implement it?

Scheduled Apex runs code at specified times using the Schedulable interface and a CRON expression. It’s commonly used to trigger a Batch Apex job on a recurring schedule (e.g., nightly data cleanup).

public class NightlyCleanupScheduler implements Schedulable {
    public void execute(SchedulableContext sc) {
        Database.executeBatch(new UpdateAllAccountsBatch());
    }
}
String cronExp = '0 0 2 * * ?'; // every day at 2 AM
System.schedule('Nightly Cleanup', cronExp, new NightlyCleanupScheduler());

10. What is the maximum number of batch jobs that can be queued or active at once?

An org can have up to 5 batch jobs queued or actively processing at the same time. Attempting to queue a 6th throws a LimitException, which is a common real-world gotcha when multiple automated processes try to launch batches simultaneously.

11. Why can’t you make a callout directly inside a trigger?

Triggers execute within the same transaction as the DML that fired them, and Salesforce doesn’t allow synchronous callouts within that context because it would leave the database transaction open and waiting on an external system for an unpredictable amount of time. The workaround is to make the callout asynchronously, typically via a Future method or Queueable class.

12. What is the difference between a checked and unchecked exception in Apex?

Apex doesn’t formally distinguish checked vs. unchecked exceptions the way Java does — all Apex exceptions are effectively unchecked, meaning the compiler doesn’t force you to catch them. However, custom exceptions (extending the built-in Exception class) are commonly used to represent expected, catchable business logic errors.

public class InvalidDiscountException extends Exception {}

if (discount > 100) {
    throw new InvalidDiscountException('Discount cannot exceed 100%');
}

13. What is the difference between DmlException and QueryException?

DmlException is thrown when a DML operation (insert/update/delete) fails, typically due to validation rules, required fields, or triggers throwing errors. QueryException is thrown when a SOQL query fails or returns unexpected results, such as calling .get(0) on an empty list or assigning a multi-row query result to a single sObject variable.

14. What is a custom exception class in Apex, and why create one?

A custom exception class extends the built-in Exception class, letting you represent specific, meaningful error conditions in your application (like InsufficientInventoryException) rather than relying on generic exception types. This makes error handling in calling code more precise and readable.

15. What happens if you don’t catch an exception in Apex?

If an exception isn’t caught, it propagates up the call stack, and if nothing catches it, the entire transaction is rolled back and an unhandled exception error is returned to the user or calling process (which can look like a generic, unhelpful error in an LWC or integration if not handled properly).

16. What is the purpose of a finally block in Apex exception handling?

Code inside a finally block always executes after the try (and catch, if triggered) block, regardless of whether an exception occurred — commonly used for cleanup logic that must run no matter what, like closing a resource or logging that an operation completed.

try {
    processRecords();
} catch (Exception e) {
    System.debug('Error: ' + e.getMessage());
} finally {
    System.debug('Processing attempt finished');
}

17. What is the difference between with sharing, without sharing, and inherited sharing?

with sharing enforces the running user’s record-level sharing rules within that class. without sharing explicitly ignores sharing rules, running with full object access regardless of the user (Apex’s default behavior if unspecified, which is why explicitly declaring it matters). inherited sharing makes the class adopt whatever sharing context it was called from, giving more flexibility for utility classes reused across different contexts.

18. Why does sharing enforcement matter in Apex, and where is it commonly tested in interviews?

Sharing rules only restrict record-level visibility, not object or field-level permissions — those come from profiles and permission sets separately. Interviewers often test whether you know that leaving a class as the Apex default (without sharing behavior) can unintentionally expose or modify records a user shouldn’t have access to, which is a real security review finding in many orgs.

19. What is Security.stripInaccessible() used for?

It’s a method that removes field values a running user doesn’t have access to from a list of sObjects before they’re returned to the client, helping enforce field-level security (FLS) explicitly in Apex — since SOQL queries themselves don’t automatically respect FLS by default.

SObjectAccessDecision decision = Security.stripInaccessible(
    AccessType.READABLE, accountList
);
List<Account> safeAccounts = decision.getRecords();

20. What is the difference between enforcing sharing rules and enforcing field-level security (FLS) in Apex?

Sharing rules (with sharing) control which records a user can see or edit based on ownership, role hierarchy, and sharing settings. FLS controls which fields on a record a user is allowed to view or edit, and must be checked separately (using Security.stripInaccessible(), WITH SECURITY_ENFORCED in SOQL, or manual Schema.sObjectField checks) since sharing rules alone don’t restrict field visibility.

Apex Interview Questions — Part 3 (Triggers Deep-Dive, Trigger Frameworks, Order of Execution, Test Classes)

1. Why is it a best practice to have only one trigger per object?

Salesforce doesn’t guarantee the execution order of multiple triggers on the same object, which can cause unpredictable behavior when logic depends on a specific sequence. A single trigger per object (delegating to a handler class) gives you full control over execution order.

2. What is a trigger handler pattern, and why use one?

A trigger handler pattern moves all business logic out of the trigger itself and into a separate Apex class, with the trigger simply calling handler methods based on the context (before insert, after update, etc.). This keeps triggers thin, makes logic testable in isolation, and avoids duplicated logic across multiple triggers.

trigger AccountTrigger on Account (before insert, after update) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    if (Trigger.isBefore && Trigger.isInsert) {
        handler.beforeInsert(Trigger.new);
    }
}

Common open-source frameworks include the Apex Trigger Actions Framework, Kevin O’Hara’s SObject Trigger Framework, and Andrew Fawcett’s fflib-based Enterprise Patterns. Most solve the same core problem: centralizing dispatch logic and preventing recursive or duplicate execution.

4. What is the correct order of execution when a record is saved in Salesforce?

At a high level: system validation rules run first, then before triggers, then custom validation rules, then duplicate rules, then the save to the database (but not committed yet), then after triggers, then assignment rules, auto-response rules, workflow rules, escalation rules, entitlement rules, and finally the commit to the database, followed by post-commit logic like sending emails.

5. Why does order of execution matter for interview scenarios?

Interviewers often test whether you understand that workflow field updates can re-trigger the save process, or that before-triggers happen before validation rules run — misunderstanding this can lead to unexpected infinite loops or values being overwritten unexpectedly.

6. How do you prevent a trigger from running recursively?

A common pattern is a static Boolean flag in a handler or utility class that’s set to true the first time the trigger logic runs in a transaction, checked at the start, and used to skip re-execution during any recursive invocation within that same transaction.

public class TriggerControl {
    public static Boolean hasAlreadyRun = false;
}

7. What is the difference between Trigger.isBefore and Trigger.isAfter?

Trigger.isBefore is true when the trigger is executing in a before-context (record not yet saved, so field changes are cheap), while Trigger.isAfter is true in an after-context (record already has its Id, but changing fields here requires a separate DML update).

8. When would you use an after-trigger instead of a before-trigger?

After-triggers are needed when you require the record’s Id (for example, to create related child records) or need to update related records rather than the triggering record itself. Before-triggers are preferred for simply modifying fields on the triggering record, since they avoid an extra DML statement.

9. What is a Test Class in Apex, and why is it required?

A test class contains methods annotated with @isTest that verify Apex code behaves correctly. Salesforce requires at least 75% code coverage across an org’s Apex code (with meaningful assertions, not just coverage) before code can be deployed to production.

@isTest
private class AccountTriggerHandlerTest {
    @isTest
    static void testAccountInsert() {
        Account acc = new Account(Name = 'Test Account');
        insert acc;
        System.assertNotEquals(null, acc.Id);
    }
}

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

They mark a boundary around the code you actually want to measure and test — code inside this block gets a fresh set of governor limits, and any asynchronous Apex called inside it (Future, Queueable, Batch) executes synchronously when Test.stopTest() is reached, letting you assert on its results immediately.

Test.startTest();
MyQueueableClass.enqueueJob();
Test.stopTest();
System.assertEquals(1, [SELECT COUNT() FROM Task]);

11. Why shouldn’t test classes rely on existing org data?

Tests that query existing records assume specific data exists, which may not be true in other environments (like a fresh sandbox or another org), causing tests to fail unpredictably. Best practice is to create all necessary test data within the test method itself, or through a @testSetup method.

12. What is @testSetup used for?

A method annotated with @testSetup runs once before all test methods in a class and creates common test data that each test method can then use, reducing duplicated setup code and improving test run performance.

@testSetup
static void setup() {
    insert new Account(Name = 'Test Co');
}

13. What is SeeAllData=true, and why is it generally discouraged?

It’s an annotation that allows a test method to access real org data instead of being isolated in its own data sandbox. It’s discouraged because it makes tests dependent on the state of the org’s data, which can change over time and cause tests to fail inconsistently across environments.

14. How do you test that an exception is thrown correctly?

Wrap the code that should throw the exception in a try-catch within the test method, and assert that the catch block was actually reached (or that no exception was silently swallowed):

try {
    insert new Account(); // missing required field
    System.assert(false, 'Expected an exception');
} catch (DmlException e) {
    System.assert(e.getMessage().contains('REQUIRED_FIELD_MISSING'));
}

15. What is code coverage, and is 75% coverage alone considered good practice?

Code coverage measures the percentage of Apex lines executed by test methods. While Salesforce enforces a minimum of 75% to deploy to production, coverage alone doesn’t guarantee correctness — tests should include meaningful assertions that verify actual behavior, not just execute lines without checking results.

16. What is the difference between unit tests and integration-style tests in Apex?

Unit tests isolate a single method or class’s logic, often using mock callouts or minimal data. Integration-style tests verify that multiple components (trigger, handler, related objects) work correctly together, closer to a real end-to-end scenario, though still run entirely within Salesforce’s test framework.

17. How do you test a class that makes an HTTP callout?

You implement the HttpCalloutMock interface to simulate the external response, then register it with Test.setMock() before the callout code runs, since real HTTP callouts aren’t allowed during test execution.

Test.setMock(HttpCalloutMock.class, new MyMockResponseGenerator());

18. What is System.runAs() used for in test classes?

System.runAs() lets you execute test code in the context of a specific user, which is essential for testing sharing rules, field-level security, and profile-based permission behavior, since test methods otherwise run as the user executing the test (often an admin with full access).

User testUser = [SELECT Id FROM User WHERE Profile.Name = 'Standard User' LIMIT 1];
System.runAs(testUser) {
    // logic tested with restricted permissions
}

19. Why should test classes cover both positive and negative scenarios?

Testing only the “happy path” (valid data, expected flow) misses bugs that occur with invalid input, missing data, or edge cases like bulk operations or null values — these negative and boundary scenarios are often exactly what breaks in production and what interviewers expect you to mention.

20. What is a trigger’s context variable, Trigger.newMap, used for?

Trigger.newMap provides a map of record Id to the new version of the sObject, available in update, before/after contexts where an Id already exists. It’s useful for efficiently looking up a specific record’s new state by Id without looping through Trigger.new manually.

Account updatedAcc = Trigger.newMap.get(someAccountId);

Apex Interview Questions — Part 2 (Governor Limits, SOQL/SOSL, Bulkification)

 

1. What are the key governor limits every Apex developer should know?

The most commonly tested ones are: 100 SOQL queries per transaction (200 for async), 150 DML statements per transaction, 10,000 DML rows per transaction, 50,000 SOQL query rows retrieved, and 6MB heap size (12MB for async). These exist because Apex runs on shared, multi-tenant infrastructure.

2. What is the difference between synchronous and asynchronous governor limits?

Asynchronous Apex (Future, Queueable, Batch, Scheduled) generally gets higher limits than synchronous Apex — for example, 200 SOQL queries instead of 100 — because async transactions run separately from the user's request and don't need to complete within the same response window.

3. What happens when a governor limit is exceeded?

Salesforce throws a System.LimitException, which — unlike most other exceptions — cannot always be caught and safely continued from, since it typically indicates the current transaction has already used up a shared platform resource. The entire transaction is rolled back.

4. How can you check how close you are to a limit at runtime?

The Limits class provides methods like Limits.getQueries(), Limits.getLimitQueries(), Limits.getDmlStatements(), and Limits.getLimitDmlStatements(), which let you inspect current usage against the maximum allowed within a transaction.

apex
System.debug('SOQL used: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());

5. Why should SOQL queries never be placed inside a for loop?

Each iteration of the loop would count as a separate query against the 100-query-per-transaction limit, so processing a batch of 200 records could exceed the limit almost immediately. The fix is to query once outside the loop and iterate over the results in memory.

apex
// Bad
for (Account a : accounts) {
    List<Contact> cons = [SELECT Id FROM Contact WHERE AccountId = :a.Id];
}

// Good
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    // group in memory
}

6. What is SOQL used for, and what are its main clauses?

SOQL retrieves records from Salesforce's database. It supports SELECT, FROM, WHERE, ORDER BY, LIMIT, GROUP BY, and relationship queries, but doesn't support arbitrary joins the way SQL does — relationships are traversed instead through parent-child or child-parent syntax.

7. How do you write a parent-to-child SOQL query?

You use a subquery with the child relationship name (usually the plural of the child object, or a custom relationship name ending in __r for custom objects):

apex
List<Account> accs = [
    SELECT Name, (SELECT LastName FROM Contacts)
    FROM Account
];

8. How do you write a child-to-parent SOQL query?

You traverse upward using dot notation on the relationship field:

apex
List<Contact> cons = [
    SELECT LastName, Account.Name, Account.Industry
    FROM Contact
];

9. What is SOSL, and when would you use it over SOQL?

SOSL performs a text search across multiple objects and fields at once, useful when you're searching for a term (like a name or keyword) but don't know which object or field it lives in. SOQL is better when you already know the specific object and filtering criteria.

apex
List<List<sObject>> results = [FIND 'Acme' IN ALL FIELDS RETURNING Account(Name), Contact(LastName)];

10. Can SOSL return records from multiple different objects in one query?

Yes — that's one of its main advantages over SOQL. The result is a list of lists, where each inner list corresponds to one of the object types specified in the RETURNING clause.

11. What is bulkification and why is it a best practice?

Bulkification means designing Apex logic to correctly and efficiently process multiple records in a single execution context — for example, a trigger firing on 200 records from a data import — rather than assuming only one record will ever pass through at a time.

12. What's a common real-world bulkification mistake in trigger code?

Placing a SOQL query or DML statement directly inside a trigger's for loop over Trigger.new. This works fine with one test record but fails in production once more than roughly 100-150 records are processed in a single batch.

13. How do you avoid DML statements inside a loop?

Collect the records that need to be inserted, updated, or deleted into a list first, then perform a single DML call after the loop completes:

apex
List<Contact> toUpdate = new List<Contact>();
for (Contact c : contacts) {
    c.Description = 'Reviewed';
    toUpdate.add(c);
}
update toUpdate;

14. What is the difference between upsert and using insert/update separately?

upsert automatically determines whether each record already exists (based on its Id or a specified external Id field) and either inserts or updates it accordingly, in a single DML statement — simplifying logic that would otherwise require separate insert and update handling.

15. What is an external ID field, and how is it used with upsert?

An external ID field is a custom field marked as an External ID in its field-level settings, typically used to match records against an external system's identifier during data integration. upsert can use this field instead of the Salesforce record Id to determine whether a match exists.

apex
upsert accountList External_Id__c;

16. What is the maximum number of records that can be processed by a single DML statement?

Apex allows up to 10,000 records per DML operation within a single transaction, which is why very large data loads are often handled through Batch Apex rather than a single synchronous DML call.

17. What is the difference between Database.insert() and a plain insert statement?

A plain insert statement fails the entire operation if even one record throws an error. Database.insert(records, false) allows partial success — records that fail don't stop the others from being inserted, and you can inspect a Database.SaveResult list to see which succeeded or failed.

apex
Database.SaveResult[] results = Database.insert(accountList, false);

18. What is a Database.SaveResult, and what information does it provide?

It's the object returned by Database.insert/update/delete calls made with partial-success mode enabled. Each result indicates whether that specific record succeeded (isSuccess()) and, if not, provides error details through getErrors().

19. Why is querying only the fields you need considered a best practice?

Retrieving unnecessary fields increases heap usage and can slow down query performance, especially with large result sets. It also avoids hitting the 50,000-row query limit unnecessarily and keeps code more maintainable by making dependencies explicit.

20. What is LIMIT in SOQL and why should you use it deliberately?

LIMIT restricts the number of records a SOQL query returns. Using it deliberately (rather than relying on default behavior) helps control performance, avoid exceeding row limits, and makes intent clear — for example, LIMIT 1 when you only expect a single matching record.

apex
Account acc = [SELECT Id FROM Account WHERE Name = 'Acme Corp' LIMIT 1];

Apex Interview Questions — Part 1 (Beginner Basics)

 

1. What is Apex and how is it different from Java?

Apex is Salesforce's proprietary, strongly-typed, object-oriented programming language used to execute flow and transaction control statements on the Salesforce platform. It's syntactically similar to Java, but it runs on Salesforce's multi-tenant servers, is tightly coupled with the database (native DML and SOQL support), and is governed by strict limits (governor limits) that don't exist in standard Java since Salesforce's infrastructure is shared across all customers.

2. What are the primitive data types supported in Apex?

Apex supports primitive types including Integer, Long, Double, Decimal, String, Boolean, Date, Datetime, Time, ID, and Blob. These behave similarly to primitives in other languages but ID is Salesforce-specific and represents an 18-character record identifier.

3. What is the difference between a List, Set, and Map in Apex?

  • List: an ordered collection that allows duplicate values, accessed by index.
  • Set: an unordered collection of unique values — no duplicates allowed.
  • Map: a collection of key-value pairs where each key is unique, used heavily for quick lookups (e.g., mapping an Id to an sObject).
apex
List<String> names = new List<String>{'Alice', 'Bob'};
Set<Id> accountIds = new Set<Id>();
Map<Id, Account> idToAccount = new Map<Id, Account>();

4. What is an sObject in Apex?

An sObject is a generic representation of any Salesforce object (standard or custom) — like Account, Contact, or MyCustomObject__c. Every Salesforce record you query, insert, update, or delete in Apex is handled as an sObject or a specific subtype of it.

5. How do you declare and use a variable in Apex?

Variables are declared with a type followed by a name, optionally initialized with a value:

apex
Integer counter = 0;
String greeting = 'Hello';
Account acc = new Account(Name = 'Acme Corp');

6. What is the difference between == and .equals() in Apex?

In Apex, == compares value equality for primitives (unlike Java, where == compares object references for non-primitives). For strings and primitive types, == and .equals() behave the same way in Apex — this is a key difference from Java that trips up developers coming from a Java background.

7. What are Apex collections and why are they important?

Collections (List, Set, Map) let you work with multiple records or values efficiently in memory, instead of processing them one at a time. They're essential for bulkification — processing many records in a single operation instead of looping DML or SOQL calls, which helps avoid hitting governor limits.

8. What is a Trigger in Apex?

A trigger is a piece of Apex code that executes automatically before or after specific DML events (insert, update, delete, undelete) occur on a Salesforce object. Triggers are commonly used to enforce business logic, validate data, or update related records automatically.

apex
trigger AccountTrigger on Account (before insert, after update) {
    // logic here
}

9. What are the different Trigger events (before insert, after update, etc.)?

Apex triggers support: before insert, after insert, before update, after update, before delete, after delete, and after undelete. "Before" events are used to modify field values on the record before it's saved; "after" events are used when you need the record's Id or need to affect related records.

10. What is the difference between trigger.new and trigger.old?

Trigger.new contains the new versions of the records (available in insert/update/undelete triggers), while Trigger.old contains the old versions of the records before the change (available in update/delete triggers). Comparing them lets you detect exactly which fields changed.

11. What is SOQL and how is it different from SQL?

SOQL (Salesforce Object Query Language) is used to query data from Salesforce's database. Unlike standard SQL, SOQL only supports querying (not full DDL/DML), and it automatically understands relationships between Salesforce objects — but it doesn't support arbitrary joins the way SQL does.

apex
List<Account> accs = [SELECT Id, Name FROM Account WHERE Industry = 'Technology'];

12. What is the difference between SOQL and SOSL?

SOQL queries one object (and its related objects) at a time and is best when you know which object(s) to search. SOSL (Salesforce Object Search Language) searches text across multiple objects simultaneously, similar to a full-text search — useful when you don't know which object contains the match.

13. What are governor limits in Salesforce, and why do they exist?

Governor limits are runtime limits enforced by Salesforce (e.g., max 100 SOQL queries or 150 DML statements per transaction) to ensure no single customer's code monopolizes shared, multi-tenant server resources. Exceeding a limit throws a runtime exception that can't be caught with normal try-catch for certain limit types.

14. What is bulkification and why does it matter in Apex?

Bulkification means writing Apex so it correctly handles multiple records at once (e.g., 200 records triggered by a bulk data load), instead of assuming only one record will ever be processed. This typically means moving SOQL/DML operations outside of loops.

apex
// Bad: DML inside a loop
for (Account a : accounts) {
    update a;
}

// Good: bulkified
update accounts;

15. What is a static variable in Apex, and when would you use one?

A static variable belongs to the class itself rather than any specific instance, and its value persists for the duration of a single transaction. It's commonly used to prevent recursive trigger execution by acting as a "flag" that's checked and set once per transaction.

apex
public class TriggerHandler {
    public static Boolean hasRun = false;
}

16. What is the difference between a class variable and an instance variable?

A class (static) variable is shared across all instances of a class and tied to the transaction, while an instance variable belongs to a specific object instance created with new and can hold different values for each instance.

17. What is an Apex constructor, and how do you define one?

A constructor is a special method that runs when an object is instantiated with new, used to set up initial state. It shares the class's name and has no return type.

apex
public class Employee {
    String name;
    public Employee(String name) {
        this.name = name;
    }
}

18. What is the difference between public, private, and global access modifiers?

  • private: accessible only within the same class (default if unspecified).
  • public: accessible within the same application/namespace.
  • global: accessible from anywhere, including other namespaces and via API — required for Apex used in managed packages or exposed externally.

19. What is a DML statement, and what are the common DML operations in Apex?

DML (Data Manipulation Language) statements modify records in the database. The common Apex DML operations are insert, update, delete, undelete, and upsert (which inserts or updates depending on whether the record already exists).

20. What is a try-catch block, and why is exception handling important in Apex?

A try-catch block lets you gracefully handle runtime errors instead of letting them crash the transaction with an unhandled exception. It's especially important in Apex because unhandled exceptions in triggers or Apex-called-from-LWC can surface confusing errors to end users if not caught and re-thrown with a clear message.

apex
try {
    insert new Account(Name = 'Test');
} catch (DmlException e) {
    System.debug('Insert failed: ' + e.getMessage());
}