Technical Debt Management and Refactoring Questions
Identifying, prioritizing, and paying down technical debt sustainably. Covers recognizing debt, making the case to invest in it, refactoring safely behind tests, and balancing debt reduction against feature velocity. Includes keeping a codebase maintainable over the long term.
What is technical debt? Give a concise definition, then name the distinct categories of debt you would track separately, with one concrete example of each, how each typically accumulates, and at least one metric or signal you would collect per category so the debt is measurable, not just described.
Sample Answer
Direct answer
Technical debt is the implied cost of extra rework caused by choosing an easy or fast solution now instead of a better one that would take longer. Like a financial loan, it has a principal (the shortcut itself) and interest (the ongoing extra cost of building on top of it until it is paid down). It shows up in several distinct categories, and treating them as one blob is itself a common mistake.
Structured elaboration
The categories worth tracking separately, because they accumulate differently and need different owners:
| Category | What it looks like | Typical accumulation path | A signal to collect |
|---|---|---|---|
| Code debt | Duplicated logic, tangled functions, inconsistent style | Rushed features, copy-paste under deadline | Cyclomatic complexity, duplication percentage |
| Design/architecture debt | Wrong module boundaries, a component doing too much | Requirements drift past the original design | Cross-module dependency count, change-amplification (how many files a typical PR touches) |
| Test debt | Missing or shallow tests, brittle end-to-end suites | Skipping tests to hit a date | Test coverage delta, flaky-test rate |
| Infrastructure/build debt | Slow builds, manual deploys, outdated CI | Infra work deprioritized versus features | Build time trend, deploy frequency |
| Documentation/knowledge debt | Undocumented decisions, tribal knowledge | Single-owner components, high turnover | Time-to-first-PR for new hires, bus-factor per component |
Two of the signals above use terms worth defining plainly: cyclomatic complexity (roughly, how many independent decision paths run through a function; more branches and loops means a higher number) and bus-factor (how many people could leave the team before no one is left who understands this component). Each category needs its own signal because a healthy test-coverage number can coexist with severe architecture debt, and vice versa; a single composite "debt score" without category breakdown hides which lever to pull.
Worked example
A payments service might show: code debt (a 400-line processOrder function mixing validation, pricing, and persistence), design debt (the pricing logic is duplicated in the checkout service because there was no shared module), test debt (checkout has 40% coverage while the rest of the codebase averages 75%), and documentation debt (the only engineer who understands the tax-calculation edge cases left the company eight months ago). Four different categories, four different remediation owners and timelines, even though a single dashboard might report "technical debt: high" for the whole service.
Trade-offs & pitfalls
The most common mistake is treating debt as inherently bad. Deliberately taking on debt to hit a real deadline, with a plan to repay it, is a normal engineering trade-off, not a failure. The pitfall is debt taken on silently, with no tracking and no repayment plan, which is what actually causes long-term damage. A second pitfall is conflating technical debt with a bug: a bug is a defect against the current spec, while debt is a legitimate design choice that becomes more expensive to live with over time.
Identify the common sources of technical debt in a software organization. For each source, give a practical indicator or metric you would monitor to detect it early, and a simple detection method you could run against an existing codebase.
Sample Answer
Direct answer
Technical debt in a real organization comes from six recurring sources: rushed delivery under deadline pressure, deferred upgrades, missing tests, duplicated solutions, knowledge loss from turnover, and shortcuts taken without a repayment plan. Each has its own early-warning signal, so detecting them requires more than one metric.
Structured elaboration
| Source | Early indicator | Detection method |
|---|---|---|
| Rushed delivery | Spike in hotfixes shortly after a release | Correlate deploy dates with incident timestamps |
| Deferred upgrades | Dependencies several major versions behind | Automated dependency-audit scan (e.g. npm outdated, pip list --outdated) run on a schedule |
| Missing tests | Coverage trending down on recently-changed files | Coverage-delta check per PR, not just a global number |
| Duplicated solutions | The same logic appears with small variations across files/services | Static duplication detection (e.g. jscpd, PMD CPD) |
| Knowledge loss | One person authors the majority of commits to a module, then leaves | Git blame / commit-author concentration analysis per module |
| Undisciplined shortcuts | "TODO" / "FIXME" / "HACK" comment density rising | Grep-based comment audit, tracked over time |
Worked example
A quarterly audit of a mid-size codebase finds: 40 unresolved TODO/FIXME comments concentrated in the billing module (undisciplined-shortcut signal), the billing module's primary contributor left the company two months prior with no documented handoff (knowledge-loss signal), and npm outdated shows the payment SDK is three major versions behind (deferred-upgrade signal). All three point at the same module, which is exactly the kind of correlated signal that should escalate a module from "routine backlog item" to "priority review," even though no single metric alone would have triggered that.
Trade-offs & pitfalls
A single detection method catches only its own blind spot: static duplication detection misses semantically duplicated logic written differently, and commit-author concentration can flag a module that's simply owned by a specialist by design, not one that's actually at knowledge risk. Combine at least two independent signals before escalating a module, rather than acting on any single metric in isolation.
You have three debt items with estimates: flaky tests causing 10% weekly CI failures; missing data validation causing 1% production failures; and duplicated preprocessing code across services costing extra maintenance time. Given limited engineering time, prioritize these three items and justify your ordering with metrics and explicit business-impact assumptions.
Sample Answer
Direct answer
Of the three, prioritize the missing data-validation issue first: it has the smallest realized-impact footprint (1% of production runs) but the highest severity per occurrence (a silent correctness failure feeding downstream decisions), followed by the flaky tests (high frequency but bounded, non-customer-facing impact), and the duplicated preprocessing code last (a real cost, but a maintenance tax rather than an active failure mode).
Structured elaboration
Score each on severity-if-it-fires versus frequency, not on frequency alone:
| Item | Frequency | Severity per occurrence | Business-impact reasoning |
|---|---|---|---|
| Missing data validation | 1% of production runs | High: silently corrupts model inputs, may not be caught until a downstream metric looks wrong | Correctness failures compound quietly; a 1% rate against high production volume is still a meaningful absolute count, and each one can propagate into a business decision before anyone notices |
| Flaky tests | 10% of weekly CI runs | Medium: blocks developer velocity, but doesn't reach production | Costs real engineer time (re-running, investigating false failures) but has a natural ceiling on damage, since it's caught before deploy |
| Duplicated preprocessing | Constant (every maintenance touch) | Low-medium: extra effort per change, risk of the two copies silently diverging over time | A tax on every future change, not an active incident risk today, so it compounds slowly rather than causing acute harm |
Worked example
Assume 50,000 production inference runs/week: 1% missing-validation failures means roughly 500 potentially-corrupted runs weekly, each with a real (if hard to quantify precisely) chance of a bad downstream decision, versus the flaky tests costing an estimated 5 engineer-hours/week in wasted re-runs and investigation, and the duplicated preprocessing costing perhaps 2 extra engineer-hours per unrelated change (rare, but recurring). The data-validation issue wins because its downside is a silent CORRECTNESS failure with unbounded downstream cost, while the other two are bounded, visible, recoverable costs; this is the same logic that puts a rare-but-catastrophic risk ahead of a frequent-but-contained one.
Trade-offs & pitfalls
A naive read might rank by frequency alone and put the 10%-frequency flaky tests first; the corrective insight a strong candidate volunteers is that frequency times bounded-severity can still be less costly than a low-frequency, unbounded-severity failure mode, especially one (silent data corruption) that resists detection by construction.
You must decide whether to rewrite or incrementally refactor a system suffering from memory leaks and frequent restarts. Outline a decision framework that includes discovering what the system actually does today, cost estimation, risk profiling, a rollback plan, testing requirements for either path, and a stakeholder communication plan.
Sample Answer
Direct answer
For a system with memory leaks and frequent restarts, don't jump to a rewrite: first characterize what the system actually does today (since "suffering from memory leaks" is a symptom, not necessarily evidence the whole design is wrong), estimate cost and risk for both paths, and require a rollback plan and testing strategy before committing either way.
Structured elaboration
- Discover current behavior first: profile the memory leak specifically (which allocation pattern, which code path) before assuming it requires a full rewrite; many "the whole system is broken" symptoms trace to a small number of specific, fixable causes (an unclosed resource handle, an unbounded cache) that an incremental fix resolves without touching the rest of the design.
- Cost estimation for both paths: incremental fix cost is usually estimable directly (profile, patch, verify); rewrite cost requires estimating re-implementation of every behavior the current system has, which is systematically harder to estimate accurately precisely because some of that behavior isn't documented anywhere except the existing code.
- Risk profiling: a rewrite of a serving system carries cutover risk (a new set of bugs, however well-tested) on top of whatever risk motivated the rewrite; an incremental fix carries lower cutover risk but the possibility the underlying design genuinely can't be patched into good health.
- Rollback plan: for the incremental path, ship the fix behind a flag or as a small, independently revertible commit, so a bad patch can be pulled within minutes without redeploying the rest of the service; for the rewrite path, keep the old implementation live and swappable (a feature flag or traffic split) for a defined post-cutover window, with an explicit automatic trigger (a crash-rate or memory-growth threshold) that forces a revert to the old path rather than relying on a judgment call made under incident pressure.
- Testing requirements for either path: for the incremental path, a load test that specifically reproduces the leak pattern, run before and after the fix, to prove it's actually resolved rather than just less frequent; for the rewrite path, a full behavioral parity test suite built from the current system's observed behavior.
- Stakeholder communication: state the finding from step 1 plainly, since "we profiled it and found a specific fixable cause" is a much easier sell than "we need to rewrite this" and should be the default recommendation whenever the profiling supports it.
Worked example
Profiling reveals the memory leak traces to a single connection pool that isn't releasing connections on a specific error path, present in roughly 15% of requests under load. That's a targeted, incremental fix (patch the error-handling path, add a regression test that specifically exercises it under load), not evidence the serving layer's overall design is unsound. The frequent restarts are a symptom of this one leak, not of pervasive architectural rot, so the decision framework correctly routes to incremental refactor once the discovery step is done, even though the initial framing ("suffering from memory leaks and frequent restarts") sounded like a case for a rewrite.
Trade-offs & pitfalls
The pitfall this question is testing is reacting to a scary-sounding symptom (frequent restarts) with a scary-sounding fix (full rewrite) without first doing the cheap diagnostic work that, in a large fraction of real cases, reveals a narrower, more tractable root cause.
Product requests a last-minute improvement for launch with a two-week deadline, and you must decide what technical debt and compromises are acceptable. List the decision criteria you would use, short-term mitigations to reduce the long-term risk, how you would document the debt you incur, and a remediation timeline to resolve it after launch.
Sample Answer
Direct answer
Under a genuine two-week deadline, decide acceptable compromises with explicit criteria (is the shortcut isolated and reversible, does it touch anything safety- or compliance-critical), apply short-term mitigations that bound the risk rather than eliminate it, document exactly what was skipped and why, and commit to a specific post-launch remediation timeline rather than an open-ended "later."
Structured elaboration
- Decision criteria: is the affected code path isolated (low blast radius) or does it touch critical infrastructure shared by other features? Is the shortcut reversible (can it be feature-flagged off if something goes wrong) or does it lock in a hard-to-undo decision (a schema change, a public API shape)? Does it touch anything with compliance or safety implications, where "acceptable" bars are much lower regardless of deadline pressure?
- Short-term mitigations: if skipping full validation testing, add a targeted monitoring alert on the specific failure mode most likely to occur; if skipping a scalability concern, add a hard rate limit or circuit breaker so a worst-case failure is contained rather than cascading.
- Documentation: what specifically was skipped, the reasoning, and the acceptance criteria from step 1, filed as a ticket at launch time, not reconstructed from memory afterward.
- Remediation timeline: a specific date (typically the very next sprint, given the urgency that created this debt in the first place), owned by name, reviewed regardless of whether other priorities have since emerged.
Worked example
A checkout-redesign launch under a two-week deadline: the team skips full regression coverage on a secondary, low-traffic payment path (saved-card autofill) and ships with full coverage on only the two highest-traffic payment paths. Given this touches a customer-facing payment flow (a moderate-to-high stakes category, though not classified as a hard compliance blocker in this case), the mitigation is a monitoring alert on the error rate for that specific payment path in production, and the documented remediation is the full regression suite completed and reviewed within the next sprint, owned by name, with an explicit note that if the monitoring alert fires before then, the affected path is disabled immediately regardless of the sprint boundary.
Trade-offs & pitfalls
The pitfall in any monitoring-as-mitigation plan is treating "we'll monitor it" as sufficient without a genuinely actionable trigger; a monitoring alert that nobody's specifically on the hook to act on the moment it fires is not meaningfully different from no monitoring at all, so the trigger needs an owner and a predefined action (pause the launch or disable the affected path), not just visibility.
Unlock Full Question Bank
Get access to all 18 Technical Debt Management and Refactoring interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.