Debugging & Logging Fundamentals
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Debugging is finding and fixing defects, and logging is recording what your program did, and together they're how you understand code that isn't doing what you expect — debugging answers why it's wrong right now, and logging answers what it did before or in production.
They matter from day one because you spend more time reading and fixing code than writing it. The debugging method I follow is a hypothesis-test cycle: reproduce the bug reliably, form a hypothesis about the cause, inspect the state with a debugger or a print, test the hypothesis, fix, and verify with a regression test.
The rule I stress is change one thing at a time — if you change two and it works, you don't know which one mattered. For logging I use levels — TRACE, DEBUG, INFO, WARN, ERROR — each with a purpose, and the big win is that levels are configurable at runtime, so I can turn DEBUG on in production without redeploying.
I log structured fields, not free text, so the logs are searchable, and I always include context with errors and pass the exception object so the full stack trace prints, because the stack trace is the most important clue when something fails.
The trade-off I'm honest about is that random trial-and-error feels fast but rarely finds subtle bugs, while the methodical cycle feels slower but actually works.
Debugging & Logging Fundamentals
Direct Answer
Debugging is the process of finding why software behaves incorrectly and verifying a fix. Logging records selected runtime events so behavior can be investigated later or in another environment. Debuggers, tests, logs, metrics, and traces answer different questions; choosing the least intrusive useful signal is part of the work.
Code Example
// (1) DEBUGGING CYCLE on a failing method:
// Reproduce: total() returns 0 for a non-empty cart.
// Hypothesis: the stream filter is dropping all items.
// Inspect: set a breakpoint on the filter, see `price > 0` rejects 0-priced
// items (a free item). Confirm: the hypothesis is correct.
// Fix: change the predicate to `price >= 0` (free items count).
// Verify: add a regression test with a free item.
public Money total(List<OrderLine> lines) {
return lines.stream()
.filter(l -> l.price().signum() > 0) // BUG: drops free items
.map(OrderLine::subtotal)
.reduce(Money.ZERO, Money::plus);
}
// (2) LEVELED, STRUCTURED LOGGING with SLF4J.
private static final Logger log = LoggerFactory.getLogger(Cart.class);
public void checkout(Cart c) {
log.info("checkout started"); // always on
log.debug("cart contents={}", c.items()); // on-demand, prod-safe
try {
pay(c);
} catch (PaymentException e) {
// ERROR with CONTEXT (not just the message) -> searchable later.
log.error("checkout failed userId={} amount={}", c.userId(), c.total(), e);
throw e;
}
}
// Structured fields (userId, amount) make logs queryable; the exception `e`
// as the last arg prints the full stack trace — the key clue.
Check your understanding
Answer these without looking back at the article.
- What are the steps of the debugging cycle, and why must you reproduce the bug first?
- Why is changing one thing at a time important when debugging?
- What are the logging levels, and what is the purpose of each?
- Why is structured logging (fields) better than free-text logging?
- Why should you log the exception object, not just the error message?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.