Lessons learned from rewriting code
In this technical retrospective (2019), @hpolatyuruk shares practical architectural lessons learned from running, scaling, or migrating production software under lean constraints.
Core Principles for Independent Builders:
Defaulting to simpler architectures (such as monolithic frameworks, SQLite, or managed VPS) prevents early engineering bankruptcy.
Evaluating true compute and egress costs reveals that high cloud bills can often be slashed by 80%+ through judicious re-platforming.
Code you write at 2 AM as a solo engineer must be easy to debug six months later without complex microservice dependencies.
Community Debate Themes:
HN engineers debated the tradeoffs between cloud convenience and raw server economics, database indexing strategies, and resisting premature architectural complexity.
CATEGORY Frugal Engineering →
HISTORICAL ERA Cloud & Mobile Era (2014–2019)
COMMUNITY SCORE 116 points
TOTAL DISCUSSIONS 132 comments
I was the main programmer on a web application with 250k lines of code; it handled a single very complex product selection/configuration process. The time came to expand it to handle multiple products in the same family of products.
We ended up, over six months, doing a complete rewrite that, from the outside, looked like refactoring (meaning, from the user's/product owner's perspective, the old stuff continued to work as we wrote the new stuff beside it, switching over as we went).
We launched on time and on budget. My last week was a death march but that was it. There was now only 50k LOC. Where the previous version was tightly coupled to the product specification, we now had an engine that could perform the same selection/configuration process for any product that could be articulated in the new DSL we'd developed; as a bonus, the specifications were standalone modules so we could use them in completely separate web apps.
The whole time we were aware that we were breaking the 'never rewrite' rules spelled out by Spolsky and others, but we were successful, the codebase took a huge leap forward both in speed of development and maintainability, and a subsequent project to add another set of products took half the time that was estimated.
I don't know what we did right, exactly, except perhaps that our rewrite was never about burnout or annoyance at the existing codebase; our decisions along the way were always justified by what we had to deliver and a reasonable feeling of an experienced dev that refactoring the old code would take more effort than starting over.
I'm not sure I buy the articles rejection of the rewrite they did, because he frames it entirely in terms of delay in updating and adding features, without seeming to consider that this was just legitimate technical debt that they had to pay off. The old code got them into a position where they could rewrite it. How many startups fail because their attempts to get it right the first time prevent them from ever launching and getting the customers that will pay to fix it later?
A pattern I've started seeing with most "bad" rewrites is that "bad" rewrites are probably usually motivated by burnout than actual architectural problems. People get tired of working on the code, and they would rather be working on something else hence "let's rewrite".
When you hear about "good" rewrites it's almost never framed in a way that is "the old code is bad and complicated." It's usually driven by an architectural need - for example it started in Python, but now we have scaled up and in order to avoid $1MM/month a rewrite in Go/Rust/C would be beneficial.
Or, in the case of changing marketing needs, it can be "we now better understand our customers and a rewrite can help us provide a more stable interface and help us iterate faster". However in this case a full 100% rewrite is never the case and it's usually done piecemeal or by the new system simply proxying requests to the old system.
When the excuse is simply the "code is a mess", then not only are you admitting that the original team failed to design a maintainable system, you are also trusting them to not make the same mistake again. And if their whole motivation is "the code is a mess", they will probably fuck up the redesign, as messy code is a symptom of bad design but not the root cause. If the root cause is burnout, and that isn't addressed, the rewrite may be just as bad.
1) Rewriting code is rarely, as the phrase suggests, about doing all the work over. Usually it is about restructuring the architecture and then adapting the old code gradually.
2) Throwing away code is not the same as throwing away knowledge. Provided that you didn’t lose the entire team, the knowledge is still there. The code is just the corpse of knowledge and it is worth just a fraction of what the team that wrote the code is.
A rewrite is a good opportunity to revisit and review that knowledge.
3) With experience you learn to balance two forces: perfection and pragmatism. You should strive for perfection in everything you do, but you have to demand of yourself that you can release in a reasonable amount of time.
4) Every good software project I’ve been on has viewed the code as something temporary. This forces you to think about how you will replace every bit of the code as you write it. From this, workable architecture emerges unless you succumb to perfectionism.
From your description your CTO’s instincts were right, but he might be missing the backbone to put you in your place.
Your team needs a more experienced lead developer to help you guys mature as developers. And you need to pay less attention to articles and blogs, be less worried about what others think and ready to learn from someone more experienced. That’s going to be hard since we programmers tend to have huge egos. Even when we’re inexperienced.
Most rewrites are unjustified, either because the old code is good enough, or because the guys in charge of the rewrite cannot deliver better (whether or not the old code is good enough).
But some are.
Arthur Whitney creates each version of the K language from scratch (he is now at version 7). They are not backward compatible, but every single one so far has been better on the metrics that he cares about (succinctness, speed, and revenue).
Everyone says that the Mozilla->Firefox rewrite was unjustified, but ... the Mozilla codebase looked like a dead end to too many people working on it (from first and second hand accounts I heard), it's not clear that it was viable to continue with the old codebase -- and, even if it was, it is unclear that Mozilla would have been better off doing that: Microsoft was the unstoppable juggernaut playing dirty. IE won not based on technical merit (although it actually WAS the better one come version 4) - but arguably because it was integrated into the OS. In fact, it is quite likely that we would not have firefox today at all if they did not start the rewrite back then.
C# is essentially a rewrite of Java; done for legal reasons rather than technical reasons, but IMO the result is a much better technical solution than trying to retrofit and comply with the (legal) legacy of Java.
But ... yes, most rewrites are unjustified and often end in failure.
Rewrites, as opposed to refactors, are warranted when they address fundamental architectural limitations that hinder product success. There is a notable exception for cases where the software architecture effectively is the product and customers rely on observable behaviors good or bad e.g. RDBMS. Poor design is not the only source of architectural problems and it is not always avoidable; maintaining unused flexibility and optionality in architectures is often extremely expensive.
In my experience, few companies rewrite systems with legitimate architecture issues. It is vastly cheaper in the short-term to redefine the business in terms of those limitations, which is what most actually do in these cases. The risk to this strategy, which I've seen manifest many times, is that a competitor without these limitations can change the expectations of the market and thereby render your product obsolete in surprisingly short order.
Rewrites are always extremely expensive, along many dimensions, but they are also sometimes unavoidable and can lead to much greater product success than without. It is much more complex than the mere state of your code base. The calculus for whether or not it is worth it doesn't lend itself to simple analysis.