When a rewrite is avoidance disguised as architecture
Learn how to distinguish justified replacement from clean-slate avoidance, prove the need for a rewrite, and treat migration as part of the architecture.
Rewrite proposals often describe the destination beautifully. There is a cleaner data model, a modern framework, clearer service boundaries, better tests, and none of the awkward decisions that make the current system difficult to change.
Then someone asks how users, data, integrations, unfinished work, and years of hidden behavior will move from the old system to the new one.
The answer is usually less detailed.
That gap is not a project-management detail to solve after the architecture is approved. The migration is part of the architecture. If the proposal explains the new system but treats the transition as a future implementation problem, it has designed a second codebase, not a replacement.
Some systems should be rewritten. A core model may no longer support the product, a platform may be unsafe or unsupported, or years of coupling may make essential changes economically unreasonable. But a rewrite becomes avoidance when the clean slate is used to escape the work of understanding what the existing system actually does.
The difficult question is not whether the old code is unpleasant. It is whether replacement is the safest and most valuable way to change the system while the business continues operating.
Why the clean slate feels like progress
Working in a mature system creates friction. A small feature touches unexpected modules. Tests are slow or absent. Names reflect a product that no longer exists. The original authors have left, and nobody can explain why one apparently unnecessary branch prevents a rare but expensive failure.
A new repository removes that friction immediately, at least for the team building it. Every abstraction is understandable because it was just created. Dependencies are current. The architecture diagram matches the code. Progress is visible through new components rather than through careful investigation of old behavior.
That feeling is real, but it can be misleading.
The new system is easiest at the moment when it contains the least knowledge. It has not yet absorbed years of product exceptions, operational lessons, customer contracts, compliance rules, failure handling, data corrections, and integrations. Comparing a mature production system with an unfinished replacement often compares accumulated reality with an idealized model.
The clean slate may reduce cognitive discomfort without reducing business complexity. When that happens, the complexity returns later as missing behavior, migration failures, duplicated logic, and a long period in which two systems must be maintained.
Legacy code contains behavior, not only mistakes
Old code deserves criticism. Some complexity is accidental. Some abstractions never helped. Some workarounds survived long after their original constraint disappeared. Age does not make a decision correct.
But unfamiliar code is not automatically unnecessary code.
A validation branch may reflect a contractual exception for one customer. A strange ordering rule may prevent duplicate settlement. A table that appears denormalized may support a reporting process that cannot tolerate the latency of a rebuilt view. A retry loop may be dangerous, or it may be compensating for a provider whose real behavior differs from its documentation.
The team should not preserve every historical quirk. It should identify which quirks carry business meaning before deciding to remove them.
Useful evidence comes from more than reading source files:
- production traces and incident history;
- real data shapes, including invalid and historical records;
- support cases and manual operational procedures;
- downstream consumers that are not visible from the main repository;
- audit, retention, and compliance obligations;
- scheduled work, reconciliation jobs, and one-off repair tools;
- behavior that customers rely on even when it was never documented.
Characterization tests can record important current behavior without claiming that all of it is desirable. They create a visible decision: preserve this behavior, intentionally change it, or remove it with evidence that nothing still depends on it.
The Drilltest behavior without freezing implementation details drill is useful here. Tests should protect the outcomes that matter while leaving room to replace the mechanism that produces them.
Diagnose the constraint before prescribing replacement
"The codebase is a mess" describes frustration, not an architectural constraint.
Before proposing a rewrite, make the current failure measurable. Different symptoms point to very different interventions.
| Symptom | Evidence to collect | What the evidence may support |
|---|---|---|
| Features take too long | Lead time by area, rework, dependency waits, review delay | Better boundaries, tests, ownership, or targeted replacement |
| Production is unreliable | Incidents by cause, recovery time, change failure rate | Repairing a few dangerous paths rather than rebuilding everything |
| The system cannot scale | Resource saturation, workload shape, hot paths, growth projections | Scaling or extracting the constrained boundary |
| Security is unacceptable | Threat model, unsupported dependencies, exposed trust boundaries | Immediate isolation, upgrade, or replacement of unsafe components |
| The data model blocks the product | Failed feature attempts, invariant conflicts, migration cost | A new bounded model with an explicit data transition |
| The platform is unsupported | Vendor dates, missing patches, operational risk | A time-bound platform migration with compatibility planning |
This analysis may still justify a rewrite. Its purpose is not to defend the old system. It is to prove that the proposed replacement addresses the cause rather than changing the technology around it.
If delivery is slow because decisions cross five teams, a new language will not repair ownership. If incidents come from one external integration, rebuilding the whole product may preserve the same failure boundary. If requirements change weekly, the new architecture will receive the same instability as the old one.
Architecture should remove a demonstrated constraint, not provide a more elegant place for the constraint to reappear.
Choose the smallest honest replacement boundary
"Rewrite the system" hides several decisions inside one phrase.
Replacing an unsupported library is different from replacing a service. Replacing one workflow is different from rebuilding the platform. Changing the persistence model is different from changing every API and user interface at the same time.
The useful boundary is usually the smallest one that has independently bad economics: it is difficult to change, important enough to justify investment, and separable enough that progress can be verified without waiting for the entire replacement.
Ask:
- Which capability is genuinely blocked by the current design?
- Can that capability be isolated behind an existing or newly created seam?
- Which data must move with it?
- Who still depends on the old behavior?
- Can traffic or users move gradually?
- What can remain unchanged without weakening the result?
This is the same judgment practised in Drillchoosing a simple solution over a clever one. The goal is not the fewest lines of code. It is the least system-level complexity that satisfies the real requirement.
A bounded replacement also creates information. The team learns whether its assumptions about the legacy behavior, target architecture, migration tooling, and operational cost were correct before committing the entire product to them.
The migration is the architecture
A replacement plan must explain how the old and new systems coexist. For a meaningful period, both will probably be real production systems with users, data, failures, and support obligations.
That coexistence raises architectural questions:
- Which system owns each piece of data during the transition?
- How are writes coordinated without silently diverging?
- How is historical data backfilled and verified?
- Can consumers tolerate both old and new representations?
- How is traffic moved, observed, and moved back?
- What happens to work already in progress during cutover?
- How are corrections reconciled when the systems disagree?
- Which operational team responds when a request crosses both systems?
- What proves that the old path can finally be retired?
Naive dual writes are especially dangerous. One write can succeed while the other fails, leaving two systems with different truths. An outbox, change-data-capture pipeline, migration queue, or explicit reconciliation process may be appropriate, but each creates its own ordering, duplication, and recovery behavior.
The migration should have observable checkpoints. Record counts alone are rarely enough. Compare business totals, invariants, sampled records, event positions, and user-visible outcomes. Define which differences are expected and which stop the migration.
The Drillmigration-safe deployment drill explores this compatibility mindset through expand-and-contract changes. The specific platform is .NET, but the central rule is broader: old and new versions must coexist safely while the transition is reversible.
Prefer learning loops over a big-bang promise
A big-bang rewrite delays the most important evidence until the moment with the largest blast radius.
The team can spend months completing features inside the new system while learning very little about production behavior, real traffic, migration throughput, operator workflows, and compatibility with consumers. Progress is measured by how much has been rebuilt, even though the real outcome is how much can be moved and safely retired.
A safer sequence is usually smaller:
- Identify one valuable but bounded flow.
- Capture the behavior and data it currently depends on.
- Create a seam that allows old and new implementations to coexist.
- Move a controlled slice of traffic or users.
- Compare correctness, performance, support cost, and delivery speed.
- Repair the migration path before increasing exposure.
- Retire the old flow only after the new ownership is proven.
Feature flags can separate deployment from release, but they do not replace migration design. The Drilldeployment versus release drill helps distinguish placing code in production from exposing it to users. A successful rewrite needs both safe exposure and a safe transfer of state.
Incremental replacement is not free. Temporary adapters, compatibility layers, and duplicated operations add complexity. The team should track and remove them. The advantage is not lower effort in every case. It is earlier evidence and smaller irreversible bets.
Know when incremental improvement is the stronger architecture
Sometimes the mature system can be improved without replacing its identity.
The team may add characterization tests around risky behavior, isolate external integrations, create clearer module boundaries, upgrade dependencies in stages, move one workflow behind an API, or replace a data-access path without rebuilding the product around it.
This work is less visually dramatic than a new platform. It can still produce better outcomes sooner.
The Drillmessy codebase drill focuses on improving the highest-risk areas while continuing to deliver product value. That approach is not an argument for endless patching. It is a way to reduce uncertainty and discover whether the alleged need for a rewrite survives contact with evidence.
Choosing incremental work also needs discipline. Without a target outcome, ownership, and time boundary, "we will clean it up as we go" becomes another form of avoidance. The team must name the constraint it is removing and measure whether the work changed it.
Know when a rewrite is justified
The alternative to rewrite enthusiasm is not rewrite prohibition.
Replacement becomes credible when several conditions are present:
- an essential product, security, scale, or operational requirement is blocked by the current foundation;
- the blocking constraint is supported by evidence rather than preference;
- smaller interventions have been tested or shown to be economically unreasonable;
- the replacement boundary is explicit;
- critical existing behavior is understood well enough to preserve or intentionally change;
- migration, coexistence, verification, rollback, and retirement are designed;
- the organization can fund the transition while still operating the product;
- success is measured by business and operational outcomes, not by launching the new stack.
A rewrite may also be the right decision when a bounded subsystem is cheap to replace and expensive to keep repairing. Not every component deserves a long strangler migration. Judgment includes recognizing when direct replacement is genuinely simpler.
What matters is that the decision can survive questions about evidence, risk, and transition. "We can build it better now" is not enough. Better must be defined, observed, and delivered without losing the behavior that keeps the business running.
Warning signs that the rewrite is avoidance
Avoidance often appears in the gaps of a proposal rather than in its stated goals.
Be cautious when:
- the case relies on adjectives such as old, messy, modern, clean, or scalable without measurements;
- technologies are chosen before the constraint is defined;
- the plan treats current behavior as a list of visible features;
- migration is a final phase owned by nobody yet;
- business and operations experts are absent from discovery;
- the team expects feature development on the old system to stop for a long period;
- compatibility layers and reconciliation are missing from the estimate;
- the replacement repeats the same ownership and dependency structure in a new stack;
- success means launching the new system rather than retiring the old one;
- there is no evidence that would cause the team to narrow, pause, or cancel the rewrite.
That final point matters. A rewrite plan should be able to change when evidence changes. The Drillchange a technical decision after new evidence drill practises exactly that form of technical humility. Continuing because too much has already been invested is sunk-cost protection, not architecture.
Make the decision reviewable
A rewrite proposal should be understandable to someone who did not participate in the original debate.
A concise decision record can include:
- The constraint. What important outcome can the current system not deliver?
- The evidence. Which measurements, incidents, costs, or failed changes demonstrate it?
- The alternatives. What targeted repairs, extractions, upgrades, or process changes were considered?
- The boundary. What will be replaced, and what will deliberately remain?
- The preserved behavior. Which business rules, contracts, and operational capabilities must survive?
- The transition. How will data, traffic, users, and ownership move?
- The safety controls. How will the team verify, pause, roll back, and reconcile?
- The economics. What does coexistence cost, and when should the investment begin paying back?
- The stop conditions. Which evidence would change or end the plan?
- The retirement criteria. What proves that the old system can be removed?
This turns a rewrite from an identity statement into a testable decision.
It also improves the conversation between engineering and product. Instead of asking the business to fund "cleaner code," the team can explain the capability being unlocked, the current cost of the constraint, the risk during transition, and the checkpoints at which value becomes visible.
How to discuss a rewrite in an interview
Interview answers about legacy systems often become too absolute. "I would never rewrite" ignores real constraints. "I would rebuild it correctly" ignores migration and hidden behavior.
A stronger answer can begin like this:
I would not decide from code quality alone. First I would identify the product or operational constraint that the current system cannot meet and gather evidence about where change is actually failing. If replacement is justified, I would choose the smallest useful boundary and design coexistence, data ownership, verification, rollback, and retirement before treating the target architecture as complete. Where incremental improvement can remove the constraint safely, I would prefer the smaller bet and use the result to decide what should happen next.
Then make the answer concrete. Describe a workflow, the behavior that must survive, the data transition, and the evidence that would allow more traffic to move. Explain what would cause you to stop.
For a behavioral question, avoid presenting simplicity as passivity. Use the Drillquality and delivery pressure drill to show how you protected the risks that mattered, reduced scope deliberately, and kept ownership of the deferred work.
A clean slate is not a transition strategy
Rewrites are attractive because they make the future easier to imagine. Architecture has to account for the present as well.
The old system is running real work. The new system begins with assumptions. Between them are users, data, integrations, operational responsibilities, and business behavior that cannot simply disappear while the team constructs a cleaner model.
Sometimes the evidence supports replacement. When it does, the rewrite should be bounded, observable, reversible where possible, and designed around migration from the beginning.
Sometimes the evidence supports a smaller intervention. That is not a lack of ambition. It may be the more architectural decision because it changes the system without asking the business to survive an unnecessary second system.
A rewrite is not architecture because the destination looks better. It becomes architecture when the team can explain why replacement is necessary, how reality will move safely, and what evidence will prove that the old system can finally be turned off.
Human editorial direction
Aporeon Guides are written and reviewed to help developers prepare practical answers and decisions, not to reproduce documentation or manufacture search traffic. Read the editorial standards.
