Cameleering / When to rewrite
Cameleering · principle 7
When a rewrite pays off: a decision checklist
"Rewrite when the numbers say so: a tool, not a creed." Most rewrites should not happen, and a few are the only way forward. This page puts the case against rewrites next to the ones that worked, a ladder of smaller steps to try first, and a checklist to run before you start.
No claim without a number.
The case against, taken seriously
In 2000 Joel Spolsky called rewriting code from scratch the worst strategic mistake a software company can make [1]. His argument still holds:
- Old code looks messy partly because reading code is harder than writing it, and its odd-looking parts often hold bug fixes learned in the field. A rewrite throws that knowledge away.
- While the new version is built, the old one stops improving. Netscape went from version 4.0 to the first beta of 6.0 with no 5.0 in between, nearly three years without a major release, and lost its market [1].
- Rewrites often ship with fewer features than what they replace; he cites Borland's Quattro Pro and Microsoft's abandoned Word rewrite [1].
Smaller teams learn the same lesson. Less Annoying CRM stopped a full rewrite in 2012 after less than a month; incremental improvements over the next few months raised signups instead [2].
When it worked, and what they measured
| case | why they rewrote | what they published |
|---|---|---|
| Cloudflare Pingora (2022), replacing an NGINX-based proxy | the old architecture limited connection reuse and was hard to extend | about 70% less CPU and 67% less memory for the same traffic; median time to first byte 5 ms lower and the 95th percentile 80 ms lower [3] |
| Dropbox Nucleus (2020), the sync engine, about four years of work | a data model older than sharing, weak consistency guarantees, hard to test, and incremental fixes had hit a ceiling | no before-and-after performance figures; lessons: exhaust refactoring first, deliver value along the way, expect feature work to slow [4] |
| Figma multiplayer (2018), only the hot path | a single-threaded server falling behind on large documents | serialization "over 10x faster"; networking stayed in Node.js and a full rewrite was dropped [5] |
| Mozilla Stylo (2017), Firefox's CSS engine | two earlier attempts to parallelize styling in C++ had failed | about 160,000 lines of C++ replaced by 85,000 lines of Rust, shipped component by component; 30% faster page load on Amazon's homepage [6] |
| Discord Read States (2020), Go to Rust | latency spikes about every two minutes from garbage collection | no more spikes, and better latency, CPU and memory after tuning; shown as graphs, with no numbers in the text [7] |
| uv (2024), a Python package installer | pip's speed | 8–10x faster than pip without a cache and 80–115x with a warm cache, by its authors' benchmark [8] |
What the successful cases share: a specific, measured limit in the old design; a baseline to compare against; and, in most of them, a partial rewrite or a component-by-component path instead of a big bang. Cameleer is one more data point: a full reimplementation that keeps Apache Camel's routes, URIs and semantics as its contract, with the before-and-after published in the ledger.
The ladder: try each rung before the next
| rung | what it means |
|---|---|
| 1. Measure | Find where the time and memory go. Knuth's famous line about premature optimization goes on to say we should not pass up the critical 3% [9]; Brendan Gregg's USE method (utilization, saturation, errors for every resource) is one way to find it [10]. |
| 2. Tune | Settings, pool sizes, memory limits, a different garbage collector, caching. |
| 3. Remove | Features nobody uses, dependencies that cost more than they give, layers that only forward calls. |
| 4. Replace one component | Martin Fowler's Strangler Fig grows the new beside the old and moves behaviour over piece by piece [11]; Branch by Abstraction puts an interface in front of the part being replaced so both can run while clients move [12]. |
| 5. Rewrite the hot path | Move only the part the measurements point at, as Figma and Mozilla did. |
| 6. Rewrite it all | Only when the design itself is the limit, the contract can be kept, and you can measure the result against the original. |
The checklist
Signs a rewrite can pay off
- You have a baseline: the current startup, memory, throughput and p99, measured on fixed hardware.
- The limit is in the design (a data model, a threading model, a runtime), and tuning has hit a ceiling.
- The contract can stay the same: the same API, formats, names and behaviour, with tests that prove it.
- You can ship in pieces, or run old and new side by side and compare.
- The gain is large and counts where you pay: many instances, many users, a hard budget at the edge or in serverless.
- The old version can keep shipping fixes while the new one is built.
Signs it will not
- No baseline, only a feeling that it is slow or ugly.
- The real problem is one hot path, a missing index or a bad setting.
- The plan changes the contract and the implementation at the same time.
- Nobody can say how the result will be measured, or what number counts as success.
- It must be finished before any of it ships.
- The main reason is a new language.
After a rewrite, publish the before and after, losses included, the way the method describes. A rewrite that cannot show its numbers has not proven it was worth doing.
Common questions
Should we rewrite our software in a different language?
Only if measurement shows the current design cannot reach a target that matters, and smaller steps have been tried. A new language is not the reason; the measured limit is, and how the code uses its language matters more than which one it is. Cloudflare, Figma and Mozilla rewrote specific parts with clear limits in view, and published what changed.
Is rewriting from scratch always a mistake?
No, but it is the riskiest option. Joel Spolsky's 2000 essay explains why: knowledge in old code is lost and the old product stops improving. Rewrites that worked usually kept the same contract, shipped in pieces, and measured the result against the original.
What is the Strangler Fig pattern?
Martin Fowler's name for growing a new system beside the old one and moving behaviour over piece by piece until the old one can be switched off. It avoids a big-bang replacement and keeps the system releasable throughout.
How do I know a rewrite was worth it?
Measure the old version first, on fixed hardware, then the new one on the same hardware with the same workload: startup, memory, throughput, tail latency, size. If the numbers are not better where you pay, it was not worth it, whatever the code looks like.
Sources
Checked on 2026-11-12.
- Joel Spolsky, "Things You Should Never Do, Part I", 6 April 2000.
- Less Annoying CRM, "A code rewrite cautionary tale", 30 July 2013.
- Cloudflare, "How we built Pingora", 14 September 2022.
- Dropbox, "Rewriting the heart of our sync engine", 9 March 2020.
- Figma, "Rust in production at Figma", 2 May 2018.
- The Rust Blog, "Fearless Concurrency in Firefox Quantum", 14 November 2017.
- Discord, "Why Discord is switching from Go to Rust", 4 February 2020.
- Astral, "uv: Python packaging in Rust", 15 February 2024.
- Donald Knuth, "Structured Programming with go to Statements", ACM Computing Surveys 6(4), 1974, doi:10.1145/356635.356640.
- Brendan Gregg, The USE Method.
- Martin Fowler, Strangler Fig Application (2004, rewritten 2024).
- Martin Fowler, Branch By Abstraction, 7 January 2014.