A reliable debugging workflow in Apex is six steps. Skip any one of them and you waste an hour.
Step 1 — Reproduce the issue reliably
Before touching the debugger, make sure you can reproduce the bug consistently. A bug that fires “sometimes” is usually a race condition, a governor limit, or a per-user permission issue. Pin it down:
- Same record? Same user? Same time of day?
- Reproducible in a sandbox? Or only in production?
- Triggered by UI, API, Data Loader, or scheduled job?
Capturing the exact reproduction path is half the work. If you can’t reproduce, you can’t debug.
Step 2 — Set the debug log trace flag
Logs aren’t captured by default — they cost too much. Enable them for the user or context you care about:
Setup → Debug Logs → New (or the User detail page → Generate Debug Log):
- Traced Entity — the user, the Automated Process, or an Apex class.
- Start Date / Expiration Date — when the trace is active.
- Debug Level — verbose-ness per category. The Default is usually
DEBUGfor Apex Code,INFOfor everything else; bump Apex Code toFINESTif you need detailed traces.
Without a trace flag, your System.debug lines run but produce no captured output.
Step 3 — Add targeted System.debug statements
The goal is to capture the values at the boundaries of the code in question:
public void run(List<Account> accs) {
System.debug('run() input size = ' + accs.size());
for (Account a : accs) {
Decimal score = computeScore(a);
System.debug('account ' + a.Id + ' score = ' + score);
if (score > 50) toUpdate.add(a);
}
System.debug('toUpdate size = ' + toUpdate.size());
update toUpdate;
System.debug('updated successfully');
}
Patterns that pay off:
- Print the size of every list, even when you “know” it.
- Print the value of every loop variable at least once.
- Print before AND after every DML or query to bracket the work.
For LWC-related Apex, the LWC is invisible from the Apex log, so put a debug line at the very top of any @AuraEnabled method.
Step 4 — Reproduce again and capture the log
Run the user action, the API call, or the test that triggers the bug. The trace flag captures the log automatically.
Open it:
- Developer Console → Logs tab → double-click the new log.
- VS Code →
>SFDX: Get Apex Debug Logs ...orsf apex tail-login the terminal. - Setup → Debug Logs → click the log id.
Step 5 — Inspect the log
Three views matter:
A. The Source View (Developer Console)
Opens the Apex file with execution markers in the gutter. You can see which lines actually ran, in what order, and how often. If a branch you expected to execute didn’t run, the Source view shows it immediately.
B. The Variables View
For any frame in the execution, see what variables held what values. The closest Apex gets to a real interactive debugger.
C. The Limits View
The running totals of SOQL queries, DML statements, CPU time, heap usage at every step. Find the spike. Almost every “the trigger is too slow” complaint has a smoking gun visible here.
For a more debugger-like experience: download the log and use VS Code’s Apex Replay Debugger — it gives you breakpoints, step over/into/out, and variable inspection over the recorded log.
Step 6 — Form a hypothesis, fix, verify, repeat
Don’t change anything until you have a hypothesis. “I think the SOQL inside the trigger is firing 200 times because Trigger.new has 200 records and the query has no IN clause.” Then:
- Make the change.
- Re-run the reproduction.
- Verify the log shows the fix (SOQL count went from 200 to 1).
- Run the test class.
- Deploy.
If the fix doesn’t work, don’t pile on more changes — back out, re-debug, refine the hypothesis.
Bonus tools for the senior toolbox
- Checkpoints — Developer Console only. Set a checkpoint on a line; next execution captures a heap dump there. No code change required.
Limits.getQueries()/getDmlStatements()/getCpuTime()— read the running totals from inside the code itself when you need to debug governor pressure.- Apex Replay Debugger in VS Code — for the most painful bugs.
sf apex tail-login the terminal — see logs land as they’re captured.- Test class with assertions — sometimes the fastest way to bisect is to write a failing test, then fix it.
Common pitfalls
- Forgetting the trace flag — running, getting no log, assuming the code didn’t run.
- Trace flag expired — flags last 24 hours; re-enable.
- Wrong traced user — for scheduled jobs and
@futuremethods, trace theAutomated Processuser, not yourself. - Log truncated at 20 MB — heavy logging trips this; raise the Apex Code level on a smaller window.
What interviewers are really looking for
The six-step process plus the tools at each step. Strong signals: (1) set the trace flag before reproducing — newcomers often skip this, (2) reproduce, capture, inspect in that order — guesswork without a log wastes hours, (3) Log Inspector / Apex Replay Debugger are the modern tools beyond raw logs, (4) Limits view for performance issues, (5) form one hypothesis at a time — don’t change three things and run.
Verified against: Apex Developer Guide — Debugging Apex, Set Up Debug Logging. Last reviewed 2026-05-17.