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.
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: one query per record
for (Contact c : Trigger.new) {
Account a = [SELECTNameFROMAccountWHEREId = :c.AccountId];
}
✓ Right: collect, then query once
Set<Id> ids = newSet<Id>();
for (Contact c : Trigger.new) ids.add(c.AccountId);
Map<Id, Account> accounts = newMap<Id, Account>(
[SELECTNameFROMAccountWHEREIdIN :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
Before trigger
After trigger
Typical use
Validate, default or change fields on the same record
Create or update related records, publish events
Record Id on insert
Not yet
Yes
Change Trigger.new fields
Yes, directly
No - read-only
DML needed to change the record
No
Yes, 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
triggerContactDuplicateCheckonContact (beforeinsert) {
Set<String> emails = newSet<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 = newSet<String>();
for (Contact c : [SELECTEmailFROMContactWHEREEmailIN :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 = newList<Contact>(); // order kept, DML on the whole listSet<Id> accountIds = newSet<Id>(); // each Id once, ready for WHERE Id INMap<Id, Account> byId = newMap<Id, Account>(
[SELECTId, NameFROMAccountWHEREIdIN :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
Keyword
Record sharing
Use it for
with sharing
Enforced
Code behind a user interface - the safe default
without sharing
Ignored
A deliberate system process that must see every record
inherited sharing
Same as the caller (with sharing if it is the entry point)
Utility classes used from many places
No keyword
Same as the caller; without sharing as the entry point
Avoid - 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
@future
Queueable
Batch
Parameters
Primitives only
Any type, including sObjects
Records from start()
Job Id to track
No
Yes
Yes
Chaining
Not allowed
One job can enqueue the next
Start another from finish()
Volume
Small tasks
Moderate
Up to 50 million records
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
@isTestprivateclassContactDuplicateCheckTest {
@isTeststaticvoid blocksASecondContactWithTheSameEmail() {
insertnewContact(LastName = 'First', Email = 'a@example.com');
Test.startTest();
Database.SaveResult result = Database.insert(
newContact(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.