Salesforce interview prep · Part 1 of 7

Apex Interview Questions and Answers

The Apex questions interviewers actually ask - governor limits, triggers, sharing, async Apex and testing - with short answers, code and follow-ups.

By Bhanu Pratap SinghLast reviewed 2026-10-2318 min read8 questions4 Beginner3 Intermediate1 Advanced
Level

The Apex questions that come up again and again, from a first developer role to a senior one. Every answer goes the same way: what the thing is, why it matters, an example, and the short version to say out loud.

Q1BeginnerApex basicsGovernor limits

What are governor limits, and why does Salesforce have them?

1What it is

Governor limits are hard caps on what one transaction may use: how many queries it runs, how many records it reads and saves, how much CPU time and memory it takes. Cross one and Salesforce stops the code with a LimitException.

2Why it matters

Salesforce is multi-tenant: thousands of companies share the same servers. The limits make sure one org's badly written code cannot slow everyone else down. They also force code to be written for real data volumes - a trigger has to cope with 200 records at once.

3Example

Shared Salesforce serversCompany ACompany BYour companyCompany DEach company gets a fair share. That share is the governor limit.
Limit (per transaction)SynchronousAsynchronous
SOQL queries100200
Records returned by SOQL50,00050,000
DML statements150150
Records processed by DML10,00010,000
CPU time10,000 ms60,000 ms
Heap size6 MB12 MB
Full guideSalesforce Governor Limits, explained simplyEvery limit in plain words, wrong vs right code, diagrams and a SOQL simulator you can try.

4In short - say this in the interview

Salesforce is multi-tenant, so governor limits cap what one transaction can use - 100 SOQL queries, 150 DML statements, 10 seconds of CPU. Going over throws an exception that cannot be caught, and the whole transaction rolls back.

Common mistake

Saying you can catch a limit exception. You cannot - you design code that stays under the limits.

Likely follow-ups
  • How do you check usage at runtime? (The Limits class, for example Limits.getQueries().)
  • Why are asynchronous limits higher?
Q2BeginnerApex basicsBulkification

What is bulkification, and why does it matter?

1What it is

Bulkified code handles many records at once instead of one at a time: it collects what it needs into sets and maps, runs one query and one DML for the whole batch, and never puts SOQL or DML inside a loop.

2Why it matters

A trigger does not get one record - it gets up to 200 at a time from a data load, an integration or a mass update. Code that queries once per record works in a demo and fails at the 101st record in production.

3Example

Wrong: 200 contacts, 200 queries200 contactsDatabaseFails at query 101: Too many SOQL queriesRight: collect Ids, then 1 query200 contactsSet of Account IdsDatabasecollect1 query
Wrong: one query per record
for (Contact c : Trigger.new) {
    Account a = [SELECT Name FROM Account WHERE Id = :c.AccountId];
}
Right: collect, then query once
Set<Id> ids = new Set<Id>();
for (Contact c : Trigger.new) ids.add(c.AccountId);
Map<Id, Account> accounts = new Map<Id, Account>(
    [SELECT Name FROM Account WHERE Id IN :ids]);

4In short - say this in the interview

Bulkification means writing code for a batch of records, not one: collect Ids into a set, query once, update a list once, and never put SOQL or DML in a loop. Triggers receive up to 200 records, so it is the only way to stay inside governor limits.

Common mistake

Testing with a single record. Always test with 200, the size a trigger batch can reach.

Likely follow-ups
  • How many records does a trigger receive at once?
  • Why is a Map<Id, sObject> so useful here?
Q3BeginnerTriggers

What is the difference between a before trigger and an after trigger?

1What it is

A before trigger runs before the record is written to the database; an after trigger runs once it has been written, but before the transaction commits.

2Why it matters

The timing decides what you can do. Before the save you can change the record's own fields for free - no DML. After the save the record has its Id and system fields, so it is the place to create or update related records.

3Example

Save clickedBefore triggerValidationSaved (has Id)After triggerCommitchange fields hereno DML neededId is knownwork on relatedOne save, in orderTrigger.new can be changed only in the before trigger; in the after trigger it is read-only.
Before triggerAfter trigger
Typical useValidate, default or change fields on the same recordCreate or update related records, publish events
Record Id on insertNot yetYes
Change Trigger.new fieldsYes, directlyNo - read-only
DML needed to change the recordNoYes, which can fire the trigger again

4In short - say this in the interview

A before trigger runs before the save, so you change fields on the record directly with no DML. An after trigger runs after the save, when the record has its Id, so you use it to work with related records.

Common mistake

Updating the same record with DML in an after trigger to change a field. Do it in a before trigger - cheaper, and no recursion.

Likely follow-ups
  • How do you stop a trigger running recursively?
  • Where do before-save flows run compared with before triggers?
Q4IntermediateTriggersScenarioBulkification

Write a trigger that stops duplicate Contacts with the same email.

1What it is

A before insert trigger on Contact that rejects any new contact whose email already exists - bulkified, so a load of 200 contacts runs one query.

2Why it matters

Duplicates break reporting and email sends. Rejecting them before they are saved costs no extra DML, and addError shows the user why, on the field itself.

3Example

ContactDuplicateCheck.trigger · Apex
trigger ContactDuplicateCheck on Contact (before insert) {
    Set<String> emails = new Set<String>();
    for (Contact c : Trigger.new) {
        if (c.Email != null) emails.add(c.Email.toLowerCase());
    }

    // One query for the whole batch, outside the loop.
    Set<String> existing = new Set<String>();
    for (Contact c : [SELECT Email FROM Contact WHERE Email IN :emails]) {
        existing.add(c.Email.toLowerCase());
    }

    for (Contact c : Trigger.new) {
        if (c.Email != null && existing.contains(c.Email.toLowerCase())) {
            c.Email.addError('A contact with this email already exists.');
        }
    }
}

4In short - say this in the interview

A before insert trigger: collect the emails in a set, run one query for matches, and call addError on each duplicate. One query for the whole batch - never one per record.

Common mistake

Querying inside the loop. A load of 200 contacts would run 200 queries and break the 100-query limit.

Likely follow-ups
  • What if two records in the same batch share an email? (Track the emails seen so far in the loop.)
  • How would you do this without code? (A Duplicate Rule with a Matching Rule.)
Q5BeginnerApex basicsCollections

List, Set and Map in Apex - when do you use each?

1What it is

The three collections: a List is ordered and allows duplicates, a Set is unordered and holds each value once, a Map stores values by key.

2Why it matters

Choosing the right one is most of bulkification. Sets collect Ids for a query without duplicates; maps let you find a record by Id instantly instead of looping through a list.

3Example

The three, side by side · Apex
List<Contact> toUpdate = new List<Contact>();          // order kept, DML on the whole list
Set<Id> accountIds = new Set<Id>();                     // each Id once, ready for WHERE Id IN
Map<Id, Account> byId = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]);
Account a = byId.get(someContact.AccountId);           // instant lookup, no loop

4In short - say this in the interview

List for ordered records you will insert or update, Set for unique values like Ids to filter a query, and Map to look a record up by Id without a nested loop.

Common mistake

Looping over one list inside another to match records. A Map turns 25 million comparisons into 5,000 lookups.

Likely follow-ups
  • What does new Map<Id, Account>(queryResult) do?
  • Can a Set hold sObjects?
Q6IntermediateSecuritySharing

What do with sharing, without sharing and inherited sharing mean?

1What it is

Keywords on an Apex class that decide whether it respects the running user's record-level sharing. with sharing enforces it, without sharing ignores it, and inherited sharing takes on the mode of the code that called it.

2Why it matters

Apex runs in system mode by default - it can see records the user cannot. The keyword is how you stop a user interface from showing records the user has no right to, while still letting a system process see everything when it must.

3Example

KeywordRecord sharingUse it for
with sharingEnforcedCode behind a user interface - the safe default
without sharingIgnoredA deliberate system process that must see every record
inherited sharingSame as the caller (with sharing if it is the entry point)Utility classes used from many places
No keywordSame as the caller; without sharing as the entry pointAvoid - the behaviour depends on who calls it

Sharing keywords cover record access only. Object and field permissions are a separate check: use WITH USER_MODE in SOQL, or Security.stripInaccessible().

4In short - say this in the interview

They control record-level sharing for a class: with sharing enforces the user's sharing, without sharing ignores it, inherited sharing follows the caller. None of them checks field-level security - that needs USER_MODE or stripInaccessible.

Common mistake

Believing with sharing also enforces field-level security. It does not.

Likely follow-ups
  • How do you enforce field-level security in a query?
  • Which sharing mode does anonymous Apex run in?
Q7AdvancedAsynchronous Apex

Future vs Queueable vs Batch Apex - when do you use each?

1What it is

Three ways to run Apex later, in the background: a @future method, a Queueable job (System.enqueueJob) and Batch Apex (Database.executeBatch).

2Why it matters

Background work gets higher limits and does not keep the user waiting - and some work, like a callout after a save, can only happen there. Each option trades simplicity for control and volume.

3Example

@futureQueueableBatch
ParametersPrimitives onlyAny type, including sObjectsRecords from start()
Job Id to trackNoYesYes
ChainingNot allowedOne job can enqueue the nextStart another from finish()
VolumeSmall tasksModerateUp to 50 million records
Batch Apex: 1,000,000 recordsChunk 1200 recordsfresh limitsChunk 2200 recordsfresh limits· · ·Chunk 5,000200 recordsfresh limitsEach chunk is its own transaction, so the limits reset every time.

4In short - say this in the interview

Future for small fire-and-forget work, Queueable when you need sObject parameters, a job Id or chaining, and Batch for very large volumes - each chunk of 200 gets fresh limits.

Common mistake

Calling a future method from Batch Apex or from another future method - neither is allowed.

Likely follow-ups
  • How do you schedule a batch job? (Implement Schedulable and call System.schedule.)
  • What is Database.Stateful for?
Q8IntermediateTestingDeployment

What code coverage does Salesforce require, and what do Test.startTest and Test.stopTest do?

1What it is

Deploying Apex to production needs at least 75% overall code coverage, every test passing, and every trigger covered. Test.startTest and Test.stopTest mark the part of a test that is the code under test.

2Why it matters

Coverage is Salesforce's guarantee that code is exercised before it reaches production. startTest gives the code a fresh set of governor limits, so test setup does not use them up; stopTest runs any queued async work immediately, so you can assert on its result.

3Example

ContactDuplicateCheckTest.cls · Apex
@isTest
private class ContactDuplicateCheckTest {
    @isTest
    static void blocksASecondContactWithTheSameEmail() {
        insert new Contact(LastName = 'First', Email = 'a@example.com');

        Test.startTest();
        Database.SaveResult result = Database.insert(
            new Contact(LastName = 'Second', Email = 'A@example.com'), false);
        Test.stopTest();

        System.assert(!result.isSuccess(), 'The duplicate should be rejected');
    }
}

4In short - say this in the interview

75% overall coverage, every test passing and every trigger covered, to deploy to production. startTest resets governor limits for the code under test, and stopTest forces queued async jobs to finish before the asserts.

Common mistake

Writing tests only to reach 75% without asserting anything. Coverage without assertions proves nothing.

Likely follow-ups
  • What does @isTest(SeeAllData=true) do, and why avoid it?
  • How do you test a callout? (Test.setMock with an HttpCalloutMock.)

Answers describe documented behaviour, checked against the sources listed. Platforms change between releases - confirm limits and versions before an interview.