The short version
- These are seven real questions, verbatim, from our free 87-question Salesforce Developer Quiz, chosen because experienced developers miss them most, and each one fails for a specific, learnable reason.
- The pattern across all seven: certification knowledge describes what the platform offers; these questions test what the platform does, at the boundaries where documentation gets skimmed.
- The recurring traps: rollbacks that give nothing back, triggers that quietly fire twice, lifecycle order that runs earlier than you think, events that outlive their transaction, and change events that are sparser than they look.
- Every question on the quiz explains why the tempting answer fails, not just which letter was right, so a miss teaches a mechanism instead of a fact.
- If you clear all seven comfortably, the other 80 are waiting, timed, free, no signup.
Why These Seven
We publish a free Salesforce Developer Quiz: 87 timed questions written by our consultants to test what the platform actually does, not what the certification guide says it offers. The scoring data has a pattern: a handful of questions fool experienced developers far more than the rest, and they all sit at the same kind of boundary, where day-to-day work quietly builds the wrong intuition. Here are seven of them, verbatim, with the mechanism behind each. Try answering before reading the resolution; that is the whole point.
1. The Rollback That Gives Nothing Back
Plain Anonymous Apex, no prior DML in the transaction. Two questions in one: what does the debug print, and what happens at the final insert?
APEX
Account a = new Account(Name = 'A1');
Savepoint sp = Database.setSavepoint();
insert a;
Database.rollback(sp);
System.debug(Limits.getDmlStatements());
insert a;
The tempting read: rollback undid the insert, so the counter is low and the record is free to insert again. The platform disagrees twice. The debug prints 3, because setSavepoint, insert, and rollback each count against the DML statement limit, and rollback never refunds consumption. And the second insert throws, because rollback removes the row from the database but never touches the in-memory variable: `a.Id` is still populated, and inserts cannot specify an Id. After a rollback, clone the record or null the Id before re-inserting.
2. The Trigger That Fires Twice Behind Your Back
A before-insert trigger counts its own firings and the records it sees. Three Contacts go in through a partial-success save; the middle one is missing its required LastName. No other automation in the org. What does the debug print?
APEX
// Trigger, before insert on Contact:
trigger CTrig on Contact (before insert) {
Counter.fires++;
Counter.seen += Trigger.new.size();
}
// Anonymous Apex (Counter has static Integer fires = 0, seen = 0):
List<Contact> cs = new List<Contact>{
new Contact(LastName = 'A'),
new Contact(),
new Contact(LastName = 'B')
};
Database.insert(cs, false);
System.debug(Counter.fires + '/' + Counter.seen);
The most common belief is `1/3`: allOrNone=false just drops the bad record from the results. The real answer is `2/5`. Required-field validation runs after before triggers, so the first attempt fires the trigger with all three records; the failure then makes the runtime roll back that attempt and re-execute the save with only the two clean records, firing the trigger a second time. Three plus two is five, across two firings, in one transaction, which also means the static counters survive both rounds. This re-fire is exactly why naive static recursion guards misbehave under partial success.
3. Where Test.startTest Limits Actually End
The synchronous limit in this context is 100 SOQL queries per transaction. Walk the counts: 99 queries before startTest, 100 inside the block, 2 after stopTest. What happens?
APEX TEST
@isTest
static void limitBoundaries() {
for (Integer i = 0; i < 99; i++) {
List<Account> a = [SELECT Id FROM Account LIMIT 1];
}
Test.startTest();
for (Integer i = 0; i < 100; i++) {
List<Account> a = [SELECT Id FROM Account LIMIT 1];
}
Test.stopTest();
List<Contact> c1 = [SELECT Id FROM Contact LIMIT 1];
List<Contact> c2 = [SELECT Id FROM Contact LIMIT 1];
}
The trap is believing startTest resets limits for everything that follows. It does not: the fresh limits exist only between startTest and stopTest, where the 100 queries fit exactly. When stopTest runs, execution resumes the original context, which has already consumed 99 of its 100 queries. The first Contact query is number 100 and squeaks through; the second is number 101 and throws an uncatchable LimitException, after stopTest. The setup queries and the block never mix; the setup queries and the teardown queries absolutely do.
4. The LWC Setter That Runs Before You Think Anything Runs
A parent renders `<c-badge label="1042">`. The child computes its label in an @api setter using a prefix that connectedCallback sets. What does the badge display on first paint?
LWC
// parent template: <c-badge label="1042"></c-badge>
// badge.html: <template><p>{label}</p></template>
export default class Badge extends LightningElement {
_label;
prefix;
@api
get label() { return this._label; }
set label(value) {
this._label = this.prefix + value;
}
connectedCallback() {
this.prefix = 'SKU-';
}
}
The tempting answer leans on a true fact: connectedCallback really does run before the first render, so surely the prefix is set in time. But the engine constructs the child, assigns public properties first, and only then calls connectedCallback. The setter runs while `prefix` is still undefined and stores the string undefined1042 in `_label`. The template then reads the getter, which returns the already-computed value and never touches `prefix` again; nothing ever re-runs the setter, so no later render corrects it. Derive in the getter, or guard the setter, but never let a setter depend on lifecycle state.
5. The Event That Outlives Its Transaction
An audit event is published with behavior Publish Immediately. Then a downstream call throws, and the whole transaction rolls back. What is the end state?
APEX
public class EscalationService {
public static void escalate(Id caseId) {
update new Case(Id = caseId, Status = 'Escalated');
EventBus.publish(new Audit_Event__e(
Detail__c = 'Escalated ' + caseId
));
RiskService.score(caseId); // throws an uncaught exception
}
}
Most developers answer that the event rolls back with everything else, because EventBus.publish feels like DML. With Publish Immediately it is not: the event is handed to the bus the moment publish returns, independent of whether the transaction commits. The Case update rolls back; the event survives; subscribers receive an audit record for an escalation that never persisted. The roll-back-together behavior people expect is Publish After Commit, which is the default for exactly this reason. Choose the behavior per event, deliberately, not by whichever the picklist offered first.
6. The Change Event That Is Emptier Than It Looks
Change Data Capture is on for Account. A user changes `Name` and clears `Phone`, leaving `Industry` untouched. Considering all future Account updates, what does this subscriber actually do?
APEX CDC
trigger AccountChangeTrigger on AccountChangeEvent (after insert) {
for (AccountChangeEvent ev : Trigger.new) {
EventBus.ChangeEventHeader hdr = ev.ChangeEventHeader;
if (hdr.changeType == 'UPDATE' && ev.Phone == null) {
NotificationService.phoneCleared(hdr.recordIds);
}
}
}
The assumption that sinks this one: UPDATE change events carry the current value of every field. They do not. Only the changed fields are populated; every untouched field arrives as null, so `ev.Phone == null` is true on nearly every Account update, and this trigger spams notifications for edits that never went near Phone. A field genuinely cleared to null is flagged in the header’s `nulledFields` list, which exists precisely because a real null and an absent field are otherwise indistinguishable in the payload. (And `diffFields` is unrelated: it marks large text fields shipped as diffs.)
7. One Method to Mock Them All
The Stub API hands you a single dispatch point for an entire class. How do you return method-specific mock values from one handleMethodCall?
APEX STUB API
public class Mock implements System.StubProvider {
public Object handleMethodCall(Object obj, String method,
Type returnType, List<Type> paramTypes,
List<String> paramNames, List<Object> args) {
return /* mocked value */;
}
}
Developers who have only used mocking frameworks sometimes conclude you must implement each method individually, or that one dispatch point cannot know what was called. The signature says otherwise: handleMethodCall receives the invoked method name, its return and parameter types, the parameter names, and the live arguments. Branch on `method` (and inspect `args` when overloads matter), return the value for that call, and one class mocks the entire interface. It is the lowest-level, most honest mocking tool the platform has, and the Stub API section of the quiz is where scores drop hardest.
The Other 80 Are Waiting
If you answered all seven cold, respect, and the full quiz will still find your gaps: 87 questions, timed, shuffled every run, free, no signup, with an explanation behind every wrong answer so a miss teaches a mechanism. Your best score saves locally, the results break down by topic, and the share line includes your time, so bring evidence when you claim senior.
Teams use it too: as an interview calibration bar and as a warm-up before architecture reviews. And if the quiz surfaces gaps you would rather close with the people who wrote it, our Salesforce practice is a free 30-minute call away.
