Debugging and Systematic Troubleshooting Questions
Diagnosing defects methodically: reproducing failures, forming and testing hypotheses, reading stack traces and logs, bisecting changes, and reasoning about error handling and edge cases. Covers a disciplined root-cause approach that applies from local bugs to production issues, distinct from embedded hardware-level debugging. A universally probed engineering-craft skill.
A production model-serving system shows nightly latency spikes while request volume stays constant. Provide a comprehensive debugging strategy considering caching policies, batch windows, background jobs, garbage-collection patterns, multi-tenant interference, and scheduled maintenance. Specify the logs and metrics you would collect and the immediate mitigations you might apply.
Sample Answer
Direct answer
Nightly latency spikes at constant request volume mean the cause is TIME-based, not LOAD-based, which immediately rules out anything that scales with traffic and points toward scheduled work (background jobs, cache expiry/warming cycles, scheduled maintenance) or periodic garbage-collection/compaction cycles that happen to cluster around the same time each night, independent of how many requests are actually arriving.
Structured elaboration
- Confirm the pattern is genuinely time-correlated, not coincidentally volume-correlated. Check request-volume metrics for the exact spike windows across several nights; if volume is truly flat while latency spikes, that rules out ordinary capacity-driven causes and strongly implicates something scheduled or periodic. Alongside the metrics, pull application-level logs for the exact spike windows specifically (not just the surrounding hours), looking for warning-level entries, retry counts, or error codes clustered in that window that a metrics dashboard alone would not surface.
- Check for scheduled background jobs running on the same host/cluster around the spike time: a cache-warming job, a log-rotation or compaction task, a batch analytics job sharing infrastructure, a scheduled backup; cross-reference the exact spike timestamps against the cron/scheduler's OWN execution logs (start time, end time, and any error or retry entries for each scheduled run), not just a summary job-history view, since a summary view can round timestamps in a way that obscures a precise correlation.
- Check caching policies specifically for a periodic expiry pattern. If a cache (in-process or shared) has a TTL (time-to-live: how long a cached entry is kept before it expires) that causes many entries to expire around the same time each night (a fixed-time cache-refresh schedule, or a TTL set relative to a fixed daily reset rather than per-entry insertion time), the resulting simultaneous cache-miss storm can produce exactly this nightly-latency-spike-at-flat-volume signature.
- Check for batch windows in any request-batching logic; if requests are batched with a time-based flush (rather than purely size-based), and something periodic changes the batch-fill rate at that hour (fewer concurrent requests overnight meaning batches take longer to fill and therefore wait longer before flushing, even at flat OVERALL system load), that's a plausible, easy-to-overlook mechanism, especially ironic since it's a case where LOWER traffic at night could paradoxically increase per-request latency via the batching wait.
- Check garbage-collection patterns for a scheduled or load-triggered full GC that happens to land at a consistent time nightly (some GC strategies trigger based on heap-growth patterns that, combined with a consistent daily traffic/memory-allocation shape, can cluster around the same hour even without being explicitly scheduled); the runtime's own GC logs (pause duration and frequency per collection cycle, not just an aggregate GC-time metric) show directly whether pause TIMING specifically clusters at the same nightly window, rather than requiring that to be inferred indirectly from latency alone.
- Check for multi-tenant interference if the infrastructure is shared: another tenant's own scheduled nightly job competing for the same underlying resources (CPU, disk IO, network) can produce a latency spike for YOUR service with zero change to your own traffic or code, visible only by checking host-level (not just your-service-level) resource metrics during the spike window.
- Check for scheduled maintenance: automated OS patching, container/host restarts, or infrastructure-level maintenance windows that a platform team runs on a schedule independent of any application team's knowledge.
Worked example
Cross-referencing spike timestamps against the platform's job scheduler shows a company-wide log-aggregation/rotation job running nightly on shared infrastructure, and host-level (not service-level) CPU and disk-IO metrics show a clear spike during exactly the same window, confirming multi-tenant interference from that job rather than anything in the service's own code or configuration. Immediate mitigation: request the shared job be rescheduled to a lower-traffic window for this service, or move this service to isolated infrastructure if the interference is severe enough to warrant it; a code-level fix wouldn't have helped at all here, since nothing about the service's own logic was the actual cause.
Trade-offs and pitfalls
The temptation to profile the SERVICE's own code first (since that's what's directly controllable) can waste significant time when the actual cause is external, shared-infrastructure interference invisible to service-level metrics alone; checking host-level, not just service-level, resource metrics during the spike window is often the fastest way to distinguish "something in my own code" from "something external happening to my host," and should be checked early given the flat-volume/time-correlated signature already points away from the service's own request-handling logic.
What signals in your logs and metrics would push you to roll back a deployment versus continue debugging the currently deployed version of a service? Give a short rubric you use in production, plus a brief example of applying it.
Sample Answer
Direct answer
Roll back when the signals show the blast radius is growing or user-facing impact is severe and the fix isn't yet proven; keep debugging in place when the signals are stable or narrowing and you have a low-risk path to more evidence. The rubric is about trend and confidence, not just the current error rate: a flat 2% error rate you understand is safer to sit with briefly than a 0.5% rate that's climbing and whose cause is still unknown.
Structured elaboration
Concrete signals that push toward rollback:
- Error rate or latency is still climbing, not plateaued, meaning the blast radius is actively growing while you investigate.
- The failure touches a critical path (payments, auth, data writes) where continued exposure risks data integrity or revenue, not just degraded UX.
- You cannot yet state a specific hypothesis for the cause. If you don't know what's wrong, you can't bound how bad it might get, and every minute of uncertainty is a minute of unbounded risk.
- The most recent deploy is a plausible cause and rolling it back is cheap (a well-tested, low-risk rollback path exists). When rollback is cheap and the cost of being wrong is high, the asymmetry favors rolling back even before the cause is confirmed.
Signals that support continuing to debug in place:
- The metric is flat or already recovering on its own (e.g., a transient dependency blip that's clearing), meaning the risk is bounded and shrinking.
- You have a specific, testable hypothesis and the evidence to confirm or reject it is minutes away, not hours.
- The failure is isolated to a non-critical path or a small, known user segment, so continued exposure has a low, well-understood ceiling.
- Rolling back has its own real cost (loses a needed migration, reintroduces a different known bug, or the rollback path itself is unproven), making rollback riskier than the current state.
Two more factors that belong in the rubric alongside the raw signals: your technical confidence in a proposed quick patch, and your monitoring capability while you wait. A patch you are highly confident in, that you can deploy and verify within minutes, tips the balance toward fixing forward even at a moderate error rate; a patch you're guessing at does not, no matter how appealing "just ship the fix" feels under pressure. Separately, if your monitoring can only tell you the aggregate error rate but not WHO is affected or WHY, you are debugging half-blind, and that itself is a reason to prefer the safer, well-understood state (rollback) over continuing to poke at a system you can't fully observe.
Worked example
A rubric applied in production: after a deploy, error rate on the checkout endpoint rises from 0.1% to 1.5% and holds flat for four minutes while you check logs. The rate is not climbing further, the failures are concentrated in one specific edge case (carts with a discount code applied), and a log line points directly at a null-handling bug in the new discount logic. Decision: continue debugging, because the blast radius is bounded, isolated, and you have a specific hypothesis you can confirm in the next two minutes. Contrast: if that same 1.5% had climbed to 6% over those four minutes with no clear pattern in the failing requests, the correct call flips to immediate rollback, because the trend is the dominant signal, not the absolute number.
Trade-offs and pitfalls
The rubric fails when applied to a single snapshot instead of a trend: a rate that looks acceptable in isolation can be five minutes from becoming a major incident, or a rate that looks alarming can already be resolving itself. The discipline is to always ask "is this getting better, worse, or staying the same" before deciding, and to treat "I don't have a hypothesis yet" as itself a strong vote toward rollback, since it means you cannot bound the risk of waiting.
Given a function that sometimes throws an exception deep inside a library you cannot modify, explain how you would instrument the codebase to capture a stack trace and the relevant contextual variables without changing the library's own code. Describe approaches for at least two of Python, Java, or Node.js.
Sample Answer
Direct answer
You can capture a stack trace and the relevant local state without touching the library's source by instrumenting at the BOUNDARY where you call into it: wrap the call site in a try/catch (or its language equivalent) that re-raises after logging, install a global/uncaught-exception hook that fires regardless of where the exception originates, or attach a debugger/tracer that breaks on the exception type without modifying any code at all.
Structured elaboration
Three complementary approaches, from least to most invasive:
- Wrap the call site, not the library. Since you control the code that CALLS into the library, even if you can't modify the library itself, catching the exception at your own call site and logging the full stack trace, the exception type/message, and any locally-relevant variables (the arguments you passed in, the current request context) before re-raising gives you a permanent, low-overhead capture point. In Python:
try: library_call(...) except Exception: logger.exception("context: %s", relevant_vars); raise. In Java: a try/catch around the call that logs viae.printStackTrace()or a structured logger before rethrowing. In Node.js: wrapping in a try/catch for synchronous calls, or.catch()on the returned Promise for async ones. - Install a global uncaught-exception/unhandled-rejection hook. This catches cases where the exception surfaces somewhere you didn't anticipate wrapping: Python's
sys.excepthook, Java'sThread.setDefaultUncaughtExceptionHandler, Node'sprocess.on('uncaughtException', ...)andprocess.on('unhandledRejection', ...). These fire regardless of exactly where inside the library the exception originated, at the cost of being a last-resort catch-all rather than a precisely-scoped one. - Attach a debugger or tracer without changing code, when you need MORE than a stack trace, such as full local variable state at the moment of the throw. Most debuggers support "break on exception" (Python's
pdbwithpython -m pdb, orimport pdb; pdb.set_trace()combined with a signal, or a conditional breakpoint set on the exception type in a full IDE debugger; Java's debugger supports exception breakpoints directly in most IDEs) which pauses execution at the exact throw site, inside the library's own code, letting you inspect the full call stack and locals without ever editing the library.
Worked example
A third-party HTTP client library occasionally raises an unlabeled ConnectionError with no useful message, and you need to know what request triggered it. Wrapping the call site: try: response = third_party_client.get(url, timeout=5) except ConnectionError: logger.exception("third_party_client failed for url=%s, timeout=%s", url, 5); raise. This doesn't change a single line of the library, but every future occurrence now logs the exact URL and timeout that triggered it, alongside the library's own stack trace, immediately giving you the context needed to distinguish "this URL is consistently unreachable" from "this only fails under a specific timeout value."
Trade-offs and pitfalls
A global uncaught-exception hook is a safety net, not a substitute for a targeted wrap at the call site: it tells you SOMETHING failed somewhere, but without the call-site context (what arguments were in play, what business operation was in progress) it's often not enough to actually diagnose the issue, only to know it happened. The debugger approach is the most informative but requires either a reproducible local trigger or an interactive session, so it's best reserved for cases where the logged stack trace alone isn't enough to form a hypothesis.
What techniques do you use to prioritize multiple concurrent bugs or incidents affecting ML systems? Describe a decision rubric considering severity, user impact, reproducibility, rollback cost, and business KPIs, and explain how you would apply it during a busy incident window.
Sample Answer
Direct answer
Prioritize concurrent bugs and incidents using an explicit rubric weighing severity, user impact, reproducibility, rollback cost, and business KPIs together, not any single factor alone, since a high-severity-sounding bug with low actual user impact and an expensive rollback can rank BELOW a "smaller" bug that's actively costing revenue and has a trivial fix available.
Structured elaboration
The rubric, and how each factor is weighed:
- Severity: how bad is the failure mode itself (data loss/corruption ranks above a cosmetic issue, a security-relevant bug ranks above a performance blip), independent of how many users are currently affected.
- User impact: how many users/requests are affected right now, and is that number growing, stable, or shrinking; a severe bug affecting a handful of users may rank below a moderate bug affecting a large fraction of traffic.
- Reproducibility: a reliably reproducible bug can be diagnosed and fixed faster (lower time-to-resolution for the same engineering effort) than an intermittent one, which affects how quickly EACH candidate bug can actually be resolved if picked next, not just how bad it is.
- Rollback cost: if a bug traces to a specific recent change, how cheap and safe is reverting that change right now; a bug with a trivial, low-risk rollback available should often be handled immediately regardless of its rank on other factors, since the fix is nearly free.
- Business KPIs: which specific business metric is being affected (revenue, a contractual SLA, a compliance requirement) and how directly; a bug affecting a metric with hard, immediate business consequences (a broken payment flow) generally outranks one affecting a softer, longer-horizon metric even at similar technical severity.
Applying the rubric during a busy incident window: first, quickly triage EVERY open issue against the rubric (a few minutes per issue, not a deep investigation), to get a relative ranking rather than working issues in the order they arrived; second, look specifically for any issue with BOTH meaningful impact AND a cheap available mitigation (like a trivial rollback), since these should jump the queue regardless of their raw severity ranking, because the cost of addressing them is so low relative to the benefit; third, re-triage periodically as the window continues, since impact and severity can both change (a bug's blast radius growing, or a rollback becoming available partway through investigation of one issue), rather than treating the initial ranking as fixed for the whole incident window.
Worked example
Three concurrent issues during a busy window: (A) a high-severity-sounding data-consistency bug affecting a small, specific edge case (low current user impact, no clear quick fix, moderate rollback risk since the change is tangled with other recent work); (B) a moderate-severity bug causing a checkout-flow error for roughly 5% of transactions (clear, growing user impact, directly hitting a revenue KPI, and traced quickly to a specific recent config change with a trivial, low-risk rollback available); (C) a low-severity cosmetic UI bug reported by a few users (minimal impact, no urgency). Applying the rubric: (B) is prioritized FIRST despite being technically "less severe" than (A) in the abstract, specifically because it combines real, growing, revenue-affecting impact with an almost-free rollback fix, making it both the highest-leverage and fastest issue to resolve. (A) is prioritized second, staffed for a proper investigation given its rollback isn't cheap and its actual mechanism needs to be understood before a safe fix can be applied. (C) is deprioritized entirely for the duration of the busy window, revisited once the window calms down.
Trade-offs and pitfalls
A common mistake is prioritizing purely by SEVERITY LABEL (treating "critical" as automatically first regardless of current impact or fix cost), which can leave a rapidly-growing, revenue-affecting, cheaply-fixable issue waiting behind a technically-severe-but-currently-narrow, expensive-to-fix one; the rubric's explicit multi-factor weighing exists specifically to avoid that trap, and re-triaging periodically (rather than committing to the initial ranking for the whole window) accounts for the fact that these factors genuinely change as an incident window progresses.
Describe a systematic, repeatable approach you use to troubleshoot an unfamiliar technical problem end to end. Cover how you observe the symptom, form and prioritize hypotheses, gather and interpret evidence such as logs, metrics, and traces, isolate the root cause, implement and validate a fix, and decide when to escalate, roll back, or write up a postmortem.
Sample Answer
Direct answer
Troubleshooting an unfamiliar problem is a loop, not a single step: observe the symptom precisely, form a small set of testable hypotheses ranked by likelihood and cost to check, gather evidence that discriminates between them, isolate the true cause, implement and validate a fix, and decide whether the incident needs a rollback, an escalation, or a written postmortem. The loop repeats: each piece of evidence should narrow the hypothesis set, not just confirm what you already believed.
Structured elaboration
- Observe the symptom precisely. Write down exactly what is wrong, in falsifiable terms: not "the API is slow" but "p99 latency (the response time slower than 99% of requests, i.e. how bad the worst cases are, not just the average) on
POST /ordersrose from 80ms to 900ms starting at 14:32 UTC, affecting roughly 3% of requests." Vague symptoms produce vague hypotheses. - Form hypotheses before you start digging. List the plausible causes given what changed recently (deploys, config, traffic pattern, dependency versions) and what the symptom rules out. A hypothesis you cannot state is a hypothesis you cannot test.
- Prioritize by expected information gain divided by cost. A five-minute log grep that could confirm or kill three hypotheses at once beats a one-hour deep profiling session that only speaks to one.
- Gather evidence that discriminates. Logs tell you what happened at a point; metrics tell you the shape of the problem over time; traces tell you where time went inside one request. Pick the instrument that actually distinguishes your live hypotheses, not the one you're most comfortable with.
- Isolate the root cause, not just a correlated symptom. A dropped hypothesis should be dropped because evidence contradicts it, not because you got bored of it.
- Implement and validate the fix against the same evidence that revealed the problem. If you diagnosed via a specific metric, watch that metric recover before declaring victory.
- Decide what happens next. If customer impact is ongoing and the fix is unproven, roll back first and diagnose second. If a similar failure could recur, or the incident had real impact, write it up so the org doesn't relearn the same lesson.
Worked example
A "the checkout page is slow" report, applied through the loop: symptom precisely stated as "median load time is normal, but a subset of loads takes 8-12 seconds, starting after this morning's deploy." Hypotheses: (a) the new deploy added a blocking call, (b) a downstream dependency degraded independently, (c) the slow subset shares a common attribute (e.g., a specific region or a large cart). A single log query grouping slow requests by attribute would discriminate between (c) and the other two in minutes, before touching a profiler. Suppose it shows the slow requests all hit a newly added inventory-check call to a dependency with no timeout: that both confirms (a) and rules out (b)/(c) as primary causes. Fix: add a timeout and a fallback; validate by watching the p99 metric drop back to baseline over the next hour, not just the fix compiling.
Trade-offs and pitfalls
The biggest failure mode is skipping hypothesis formation and going straight to your favorite tool (attaching a profiler because you're comfortable with it, even when a five-minute log check would have ruled out two hypotheses first). The second is treating the first correlated signal as the cause without checking whether it's actually causal. Under time pressure it's tempting to fix the first plausible thing you see; that's fine as a mitigation, but the loop isn't complete until you've confirmed the metric recovered and understood why, or you will be back debugging the same symptom next week.
Unlock Full Question Bank
Get access to all 29 Debugging and Systematic Troubleshooting interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.