Salesforce Developer Interview Questions: The Apex Half
Eight Apex interview questions Salesforce hiring managers really ask, each with a short spoken answer, code that runs, and a challenge where you practice it.
TL;DR
Most Salesforce developer interviews spend half their time on Apex. The questions repeat: bulkification, trigger context, collections, recursion, partial DML failure, exceptions, and sharing. This post gives you a short spoken answer and working code for eight of them, plus a challenge for each so the answer comes from your hands and not your memory.
What The Apex Half Looks Like
A Salesforce developer interview usually has three parts. Someone asks about your background. Someone asks about the platform. And someone asks you to talk through Apex.
That third part is the one people fumble. It is rarely a trick. The interviewer wants to hear that you have written code that ran against real data, and that you know what breaks when 200 records arrive at once.
Two habits help more than any list of facts.
Answer in two sentences, then stop. Say the rule, say why it exists, and let them ask for more. Long answers hide the thing you got right.
Reach for code. If there is a whiteboard or a shared editor, use it. Five lines of Apex settle a question that three minutes of talking will not.
The questions below are the ones that come up again and again. Each one links to a challenge on Lightning Challenge where you write the answer yourself. If you are working toward the certification at the same time, the PD1 prep path covers the same ground in exam form.
Question 1: What Happens When A Trigger Runs On 200 Records?
The answer: the trigger body runs once, not 200 times. Trigger.new holds
all the records in the batch. Code that assumes one record is the single most
common bug in Apex.
This is the question behind the question. The interviewer is checking whether you write for a batch or for a demo.
Here is the shape they want to see. Query once, build a map, then loop.
Map<Id, Account> accountsById = new Map<Id, Account>(
[SELECT Id, Name FROM Account LIMIT 100]
);
List<Contact> contacts = [SELECT Id, AccountId FROM Contact LIMIT 100];
for (Contact c : contacts) {
Account parent = accountsById.get(c.AccountId);
if (parent != null) {
System.debug(parent.Name);
}
}The query sits outside the loop. The map does the matching. Two records or two hundred, the query count is the same.
Salesforce gives a transaction 100 SOQL queries. A query inside a loop over 200 records asks for 200, so it dies partway through. Say that number out loud. It shows you have read the limits and not just heard of them.
Practice the pattern on contacts by account.
Question 2: Trigger.new Or Trigger.oldMap?
The answer: Trigger.new is what the record is about to become.
Trigger.oldMap is what it was, keyed by Id. You compare the two to find what
changed.
Trigger.old exists too, but the map version is what you want, because you can
look a record up by Id instead of hunting through a list.
trigger AccountRatingWatcher on Account (before update) {
for (Account updated : Trigger.new) {
Account previous = Trigger.oldMap.get(updated.Id);
if (updated.Rating != previous.Rating) {
updated.Description = 'Rating was ' + previous.Rating;
}
}
}Expect a follow-up: which contexts have which variable? Trigger.oldMap is null
on insert, because there is no previous version. Trigger.new is null on
delete. Getting that right in the room is worth more than it sounds.
The other follow-up is why this one is before update and not after update.
In a before trigger you change the record in memory and Salesforce saves it for
you. No DML, no second trigger run.
Question 3: List, Set, Or Map?
The answer: a list keeps order and allows duplicates. A set drops duplicates and has no order. A map stores values under keys you choose.
Say what each one costs you, not just what it is.
List<String> names = new List<String>{ 'Acme', 'Acme', 'Globex' };
Set<String> uniqueNames = new Set<String>(names);
Map<String, Integer> lengthByName = new Map<String, Integer>();
for (String name : uniqueNames) {
lengthByName.put(name, name.length());
}
System.debug(uniqueNames.size());
System.debug(lengthByName.get('Acme'));That list has three entries. The set has two. Turning a list into a set is the one-line way to remove duplicates, and it is a fine thing to reach for when someone asks you to dedupe on the spot.
Maps are the ones that earn you the job. Almost every bulkified trigger is a map lookup wearing a costume. Our Apex maps guide walks through the syntax, and the collections lessons drill it.
Try set operations for the difference in practice.
Question 4: How Do You Stop A Trigger Running Twice?
The answer: a static Boolean that the trigger checks before it does work. Static variables live for one transaction, so the flag resets on its own.
Recursion happens when your trigger updates a record, which fires the trigger, which updates the record again. Workflow and flow can set it off too.
public class RecursionGuard {
private static Boolean hasRun = false;
public static Boolean shouldRun() {
if (hasRun) {
return false;
}
hasRun = true;
return true;
}
}Two things make this answer strong. First, name the lifetime: static state ends when the transaction ends, so the next user's save starts clean. Second, admit the cost. A flag that blocks the second run also blocks a legitimate second run, so a batch job that updates the same record twice on purpose will quietly skip the second pass.
Write one yourself on trigger recursion guard.
Question 5: One Record Fails. What Happens To The Other 199?
The answer: by default the whole thing rolls back. insert accounts is all
or nothing. To save the good records and collect the bad ones, use
Database.insert with the second argument set to false.
List<Account> accounts = new List<Account>{
new Account(Name = 'Has A Name'),
new Account()
};
Database.SaveResult[] results = Database.insert(accounts, false);
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
for (Database.Error err : sr.getErrors()) {
System.debug(err.getMessage());
}
}
}The second account has no Name, so it fails. With plain insert, the first one
would roll back with it. With false passed in, the first one is saved and the
second one comes back in the results with an error you can log or show.
Interviewers like this question because it has a real decision in it. Partial success is right for an import and wrong for a payment. Say which you would pick and why.
Complex DML error handling is the challenge for this one.
Question 6: How Do You Handle An Exception Properly?
The answer: catch the type you expect, do something about it, and never swallow it silently. An empty catch block turns a loud failure into a quiet wrong answer.
try {
Account a = new Account();
insert a;
} catch (DmlException e) {
System.debug('Insert failed: ' + e.getDmlMessage(0));
throw new IllegalArgumentException('Account needs a name');
}Catch DmlException, not Exception. A narrow catch says you know what can go
wrong on that line. A broad one catches bugs you meant to see.
The strongest version of this answer mentions what you cannot catch. A limit exception is not catchable in any useful way, so you design so it never fires. That is the same bulkification point from question one, arriving from the other direction.
Start with safe division handler, which is the smallest honest version of this pattern.
Question 7: With Sharing Or Without Sharing?
The answer: with sharing makes the class respect the running user's record
access. without sharing ignores it. Apex runs in system mode by default, so if
you write neither, the class may see records the user cannot.
public with sharing class ContactFinder {
public List<Contact> findByLastName(String lastName) {
return [SELECT Id, LastName FROM Contact WHERE LastName = :lastName];
}
}Default to with sharing on anything a user triggers. Reach for
without sharing on purpose, for a rollup or a system job that has to see
everything, and leave a comment saying why.
Field level security is a separate thing, and saying so is a good signal. Sharing controls which records. It does not control which fields, and it does not filter what your SOQL returns unless the class is declared for it.
Question 8: The Live Coding Question
At some point they stop asking and hand you an editor. The task is usually small: group these records, find the highest one, count the ones that match.
Grouping is the most common of them.
List<Contact> contacts = [SELECT Id, AccountId, LastName FROM Contact LIMIT 200];
Map<Id, List<Contact>> byAccount = new Map<Id, List<Contact>>();
for (Contact c : contacts) {
if (!byAccount.containsKey(c.AccountId)) {
byAccount.put(c.AccountId, new List<Contact>());
}
byAccount.get(c.AccountId).add(c);
}
System.debug(byAccount.keySet().size());The containsKey check is the part people forget. Without it you overwrite the
list every time and end up with one contact per account.
Talk while you type. Say what the map keys are before you build it. An interviewer who can follow your thinking will forgive a typo. One watching silence will not.
Group accounts by industry is the same shape with a different key.
What They Are Really Asking
Every question above is one question in disguise: have you written Apex that ran against data you did not control?
You cannot fake that in a room. You can build it in an afternoon, though, because the fix is the same as the preparation. Write the bulk version. Break it on purpose. Read the limit error. Do that a dozen times and the answers stop being recalled and start being obvious.
Two more things worth having ready. Know why Test.startTest and Test.stopTest exist, because testing comes up in nearly every loop. And be able to write a parent to child query without looking it up.
Where To Practice Next
- PD1 prep path — the same material in certification order, which doubles as interview prep.
- Apex fundamentals path — start here if the collection questions felt shaky.
- SOQL parent to child — the query shape interviewers ask for by name.
About Warren Walters
Salesforce MVP and transformative mentor with 8+ years in the Salesforce realm. Founder of Lightning Challenge, dedicated to nurturing the next generation of Salesforce talent through hands-on practice and real-world coding challenges.
Visit Profile →