Skip to main content

SF-0316 · Concept · Easy

Why do we use system.debug()?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

System.debug() writes a message to the debug log at the point where it’s called. It’s the lowest-friction way to see what your Apex code is doing at runtime — what variables hold, which branches run, where exceptions originate. Every Apex developer uses it. Most overuse it.

The basic usage

System.debug('Hello');
System.debug('account count = ' + accs.size());
System.debug(myAccount); // sObjects, lists, maps all stringify automatically

Output lands in the debug log as a USER_DEBUG event with the source line number and the message you passed.

With an explicit logging level

System.debug(LoggingLevel.WARN, 'unexpected null industry on account ' + a.Id);
System.debug(LoggingLevel.ERROR, 'callout failed: ' + e.getMessage());

LoggingLevel values: ERROR, WARN, INFO, DEBUG (default), FINE, FINER, FINEST. The platform only outputs a message if the trace flag’s level for the Apex Code category is at or above the message’s level.

What it’s good at

  • Variable inspection — print a value, see what it is.
  • Branch tracingSystem.debug('hit if branch') confirms execution reached a particular point.
  • Loop visibility — print each iteration’s key values.
  • Exception loggingSystem.debug(e.getStackTraceString()) in a catch block.
  • Pre/post DML state — print a record before insert, then after.

What it’s not good at

  • Replacing a debugger. It’s printf-style debugging. For complex execution paths, the Apex Replay Debugger in VS Code is better.
  • Production logging. Debug logs are ephemeral and require a trace flag. Real production logging goes to Platform Events, a custom Log__c object, or a third-party tool like Pharos or Logger.
  • Long-running batches. Heavy System.debug in a 100-execution batch fills the 20 MB log cap.

A common pattern

Trace at the boundaries of every method:

public Decimal computeTax(Account a, Decimal amount) {
    System.debug('computeTax IN: a=' + a.Id + ' amount=' + amount);

    Decimal rate = lookupRate(a.BillingCountry);
    Decimal tax  = amount * rate;

    System.debug('computeTax OUT: rate=' + rate + ' tax=' + tax);
    return tax;
}

When something goes wrong, you see the inputs and outputs of every method on the call path.

Performance impact

System.debug is not free. The platform still has to format the string and write it to the log, even if the trace flag is disabled. In tight loops:

for (Account a : Trigger.new) {
    System.debug('processing ' + a); // BAD if Trigger.new has 200 records
    // ...
}

This adds milliseconds per call and 100s of KB to the log. Two mitigations:

  1. Guard with a class-level flagif (Logger.enabled) System.debug(...); so production turns it off entirely.
  2. Use higher levelsSystem.debug(LoggingLevel.FINEST, ...) so the platform skips formatting when the trace level is below FINEST.

Logger pattern in mature codebases

public class Logger {
    public static void info(String msg) {
        System.debug(LoggingLevel.INFO, msg);
    }
    public static void error(String msg, Exception e) {
        System.debug(LoggingLevel.ERROR, msg + ' :: ' + e.getMessage() + '\n' + e.getStackTraceString());
        // Also: insert Log__c, or EventBus.publish a System_Log__e
    }
}

Logger.error('failed to process order', e);

This centralizes the message format, makes “where do logs go?” a single config decision, and lets you swap to platform-event-based logging without rewriting every callsite.

When to remove System.debug

Generally: never, but tune the level. The accepted view is:

  • Keep LoggingLevel.INFO and above messages in production code — they help when you need to triage later.
  • Don’t ship LoggingLevel.DEBUG or FINEST calls with expensive string concatenation — wrap them in a flag check.
  • Remove temporary debug prints added during a bug hunt — they make the code noisy.

Common antipatterns

// BAD — formats the string even when the trace is off
System.debug('account=' + JSON.serialize(a));

// BETTER — formats lazily only when DEBUG level is on
if (Logger.debugEnabled) System.debug('account=' + JSON.serialize(a));

// BAD — debugging inside a tight bulk loop with no level guard
for (Order o : orders) System.debug(o);

// BAD — printing entire collections
System.debug(Trigger.new); // serializes all 200 records

What interviewers are really looking for

The basic answer is “write to the debug log to trace what code did.” Strong signals: (1) logging levels and how the trace flag’s category-level filters control output, (2) the cost — formatting still happens even when output is filtered, so guard expensive calls, (3) production logging belongs in a Log__c object or Platform Event, not raw System.debug, (4) Apex Replay Debugger is a step up from printf-style debugging for complex bugs.

Verified against: Apex Developer Guide — Using System.debug, Debug Log Levels. Last reviewed 2026-05-17.