How 'Just Make It Work' Becomes the Most Expensive Decision You Make
Speed is the religion of software engineering. Move fast. Ship features. Get it working and get it out. And honestly? I get it. Velocity matters. Features fund everything. A working system in production beats a perfect system still in review.
I believed that. I still believe parts of it.
But there’s a version of “move fast” that doesn’t show its costs immediately. It shows them six months later, when a change that should take one afternoon takes three days. Or when a new team member spends their first two weeks just trying to understand what the system is doing. Or when your best engineer (the one who actually understands how everything fits together) starts looking a little tired, a little detached.
Everything works. Nothing is on fire. And yet something is wrong.
I’ve felt that. Probably you have too. For a long time I didn’t have a name for it.
Reading A Philosophy of Software Design gave me the vocabulary. This post is what happened when I went looking for evidence: whether this pattern was documented beyond one book, whether the companies I’d heard about had actually paid the price for it, and whether any of them had found a way out. I’ll walk through the core concepts, a few real-world cases where the bill came due, what it does to the engineers living inside these systems, and where this shows up specifically in infrastructure and platform work.
There’s a name for it
John Ousterhout, a Stanford computer science professor who spent decades building real systems before teaching, calls it tactical programming [1].
The idea is simple: a mindset where the primary goal is to get something working as fast as possible. The next feature, the next ticket, the next sprint. Each individual shortcut seems harmless: a quick hack here, a workaround there, a comment that says “TODO: fix this later” that nobody ever fixes. The code works. The sprint closes. Everyone moves on.
The problem is that complexity is cumulative. Each tactical decision is a small withdrawal from a shared account. The balance drops slowly, invisibly, until one day you try to make a “simple change” and discover that you have to touch fourteen different files, that half the logic is hiding in a place nobody thought to document, and that you genuinely aren’t sure what will break if you proceed.
The alternative is strategic programming, an investment mindset. The goal isn’t just working code today, but a system that keeps working and keeps growing cleanly. This means taking a bit more time upfront to design things properly, fixing problems at the root instead of patching around them, and treating complexity as an enemy to be eliminated, not a tax to be quietly paid.
The key insight is that strategic doesn’t mean slow. The investment is around 10–20% more time upfront. In exchange, you develop faster within months, because you stop paying the accumulated tax on every future change. The book doesn’t argue against shipping. It argues against the kind of shipping that mortgages the future to pay for the present.
How it compounds
Tactical decisions don’t break systems immediately. They degrade them slowly through three symptoms: change amplification (a simple change requires touching a dozen places), cognitive load (understanding any one thing requires understanding everything around it), and unknown unknowns (you make a change that seems isolated and discover two weeks later it broke something you had no reason to suspect was connected). I’ve written about these in more detail here, so I won’t repeat them fully. The short version: each symptom feeds the next, and none of them are visible until you’re already deep in debt.
Ward Cunningham, who invented the technical debt metaphor in 1992, put it plainly: “Shipping first-time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt.” [2]
There’s a subtlety in Cunningham’s framing that often gets lost: he wasn’t arguing against shipping. He was arguing against not cleaning up after yourself. Debt taken consciously and repaid promptly is a tool. Debt taken unconsciously and deferred indefinitely is a trap.
The “tactical tornado”
There’s also a name for the engineer who embodies this pattern at its most extreme: the tactical tornado.
You probably know one. They ship constantly. They close tickets faster than anyone else. Management loves them because their velocity is visible and measurable. When asked why something works, the answer is usually “just trust it, it works.” When you inherit their code, you discover that “it works” meant “it worked for the specific case I tested.”
Tactical tornadoes are often celebrated as heroes. The engineers who come after them (who slow down to actually understand the system, to refactor the tangled parts, to pay down the debt) appear to be making slower progress by comparison. They’re often the real heroes. They’re rarely recognized as such.
Ask any room of engineers how many have worked with a tactical tornado. Almost every hand goes up.
Companies that paid the bill
This pattern plays out at individual, team, and organizational scale. A few documented examples make the cost concrete.
Booking.com’s Perl monolith
Booking.com, headquartered in Amsterdam and one of the world’s largest travel platforms, grew fast, and the codebase grew with it. Their own engineering team is candid about the result: “a majority of our business logic still sits in a Perl monolith.” The Perl choice made sense in 2000. By 2020, it was a 20-year-old tactical decision accruing interest every day.
In 2024, they published the story of migrating a single endpoint from that monolith to Java. That endpoint (the Update Management API) had been introduced when automatic app updates didn’t exist yet. Fourteen years later, it was serving 21 different functionalities, owned by 7 different teams, with a codebase their own engineers described as “spaghetti.” The migration delivered a 30% improvement in response times, 77% smaller payloads, and a million fewer database calls per day. Not because the new code was magic, but because the old code had been accumulating tactical additions for over a decade, and the bill had grown enormous. [3]
Their post-mortem observation: the most time-consuming part wasn’t the migration itself. It was discovering all the hidden dependencies nobody knew existed, the unknown unknowns that had been accumulating for over a decade.
Facebook’s course correction
“Move fast and break things” was Facebook’s engineering motto for years. Mark Zuckerberg described it as his “prime directive” in 2009: unless you’re breaking things, you’re not moving fast enough.
By 2014, the codebase had become unstable, hard to understand, and painful to work with. On May 2, 2014 (the company’s tenth birthday), Zuckerberg changed the motto to “Move fast with stable infrastructure.” Facebook CTO Andrew Bosworth wrote about what drove it: “McDonald’s doesn’t sell the most hamburgers because they are the best. They sell the most hamburgers because they are the most reliable… High value work tends to stop when the underlying infrastructure is unstable.” [4]
The tactical approach had worked for a long time. Then it stopped working, and reversing it was expensive.
Knight Capital: 45 minutes, $440 million
On August 1, 2012, Knight Capital was the largest trader in U.S. equities, handling around 17% of all NYSE volume. By 10:15 AM, the company had lost $440 million, and within months had been acquired and effectively ceased to exist.
The proximate cause was a deployment error. The underlying cause was almost a decade of tactical decisions: dead code from a test algorithm called “Power Peg” (designed to buy high and sell low) that had been unused since 2003 but was never deleted. A flag repurposed to activate new code that accidentally activated the old algorithm instead. No automated deployment tools. No documented incident response. No kill switch. [5]
Any single strategic investment (deleting dead code, automating the deployment, adding a kill switch) would have prevented it. None were made. The interest on a decade of shortcuts came due in 45 minutes.
What it does to the people, not just the code
The debt metaphor is useful because it frames this as an economic problem. But there’s a human cost that the metaphor doesn’t fully capture.
Engineers want to build things they’re proud of. That’s not idealism, it’s how this work actually functions. When you spend most of your time navigating complexity that shouldn’t exist, you’re not really engineering anymore. You’re maintaining. You’re not solving interesting problems, you’re fighting the codebase. You’re not growing, you’re treading water.
The data on this is consistent enough that it’s hard to dismiss. Surveys of software engineers reliably find that code quality ranks as one of the top drivers of job satisfaction, often above compensation, while technical debt is just as reliably the top frustration. More concretely: a significant share of engineers have left jobs specifically because of it, and for many of them it was the primary reason. The CodeAhoy developer survey captured the raw version of this: “The system I’m working on is indeed soul-crushingly burdened. Everyone knows this: there’s a survey every year and ‘crushing technical debt’ is always the top complaint.” And: “Management actively acknowledges it… but the technical debt items never make it into work streams because it’s never seen as important enough. I actually left because of their inability to make a choice.” [6]
What makes this particularly damaging is who leaves first. The engineers with the most options (the ones who understand the system deeply, who see the problem clearly, who could fix it) are the ones with the easiest time finding another job. A tactical culture optimizes for short-term output and pays for it with long-term talent. And once the engineers who understand the system are gone, the unknown unknowns multiply.
ING, the Dutch bank headquartered in Amsterdam, confronted this directly when they began their transformation in 2015. Their CIO Peter Jacobs described what had gone wrong: “Somehow over the years, success in IT had become a question of being a good manager and orchestrating others to write code.” The engineering culture had been hollowed out. Engineers were coordinators, not builders. The result: five or six “big launches” a year, slow delivery, a widening gap between ING and digital-native competitors.
The strategic response was a complete cultural rebuild modeled on Spotify’s engineering model: 350 autonomous squads, continuous deployment, and a deliberate shift back to craft. As their CIO put it, “We consciously encouraged people to go back to writing code. I did it myself, and have made it clear that engineering skills and IT craftsmanship are what drive a successful career at ING.”
By 2018, ING was running 30% leaner with higher productivity, had grown its primary customer base by 60%, and had built a reputation as one of the best engineering employers in the Netherlands, on par with Google and Netflix according to one academic paper on the transformation. [7]
Where this shows up in your infrastructure
If you work in platform or infrastructure engineering, tactical programming has a specific texture you’ll recognize: servers that nobody wants to touch, configuration that exists nowhere in writing, changes made through a web console that left no trace.
Martin Fowler coined the term snowflake servers for manually configured infrastructure that becomes impossible to reproduce: “The true fragility of snowflakes comes when you need to change them. Snowflakes soon become hard to understand and modify.” His prescription was phoenix servers: infrastructure regularly rebuilt from automated recipes, so it can never accumulate unknown state. [8]
Corey Quinn at Duckbill Group described the infrastructure equivalent of tactical programming as “ClickOps” (managing cloud infrastructure by clicking around in the web console). He’s funny about it, but the point is sharp: ClickOps creates the infrastructure equivalent of undocumented code. Changes happen. Nobody knows what state things are in. The blast radius of any future change is unknown.
IaC, GitOps, and platform engineering aren’t buzzwords. They’re the strategic response to accumulated tactical infrastructure debt. The same principle applies at the infrastructure level as at the code level: invest in the right foundation now, so that every future change compounds on something solid rather than something fragile.
Adyen, the Amsterdam-based payments company, built this instinct into their DNA from the start. When they founded the company in 2006, the global payments industry was a patchwork of legacy systems, each processor bolted onto the next with tactical integrations accumulated over decades. Their founding bet: build a single unified platform, entirely in-house, from scratch. That upfront investment meant it took five years to reach profitability. It also meant that every new payment method, every new market, every new capability was added to a foundation designed to grow, not stitched on as another layer of tactical debt. Today they process over €1.2 trillion annually at a 50% EBITDA margin. That is what a decade of compounding strategic investment looks like.
What you can actually do
The honest answer is that you can’t unilaterally transform an organization’s engineering culture. If you’re an IC, you don’t control the product roadmap, the sprint planning, or what management decides to prioritize.
But there are things you can actually do, today, without needing permission.
Name the problem. One of the most useful things about having this vocabulary is that it makes conversations actionable. “This is change amplification: every time we need to touch this area, we have to update it in five different places” is a more productive conversation than “this feels messy.” Give the symptom a name, and it becomes something you can discuss concretely without it turning into a culture war.
Make small strategic investments. The recommended investment is around 10–20% of development time on strategic improvements. You don’t need to propose a refactoring sprint or a tech debt quarter to do this. When you’re already working in an area, take the extra twenty minutes to fix the one thing that keeps biting you. Clean up the function while you’re already in the file. Write the comment that would have helped you six months ago. These are compound interest in the other direction.
Resist the pressure to patch. The tactical instinct is to find the fastest path from current state to passing tests. The strategic instinct is to ask: is this the right fix, or just a fix? They’re not always different. But asking the question costs nothing and occasionally saves a lot.
Document the unknown unknowns. When you discover that a change you made had a non-obvious dependency somewhere else, write that down somewhere visible. That’s a direct attack on one of the three symptoms. Future-you and your teammates will be grateful.
None of these are dramatic. They don’t require a leadership buyoff or a process change. They’re the engineering equivalent of what I wrote about in an earlier post: the quiet, unscalable investments that compound over time.
The mindset shift
This isn’t really about code quality for its own sake. It’s about what makes software engineering sustainable.
Tactical programming feels faster in the short run because it is faster in the short run. But the speedup is borrowed from the future. Each tactical decision makes the next one slightly more expensive: more cognitive load, more change amplification, more unknown unknowns. The interest compounds until development velocity collapses under its own weight.
Strategic programming feels slower in the short run because it is slower in the short run. But the investment pays dividends almost immediately. The development speed improvements start appearing within a few months, and they don’t stop appearing. You’re not just building the current system. You’re building the capacity to keep building well.
The companies that understood this early (that saw their infrastructure and their codebase as foundations to invest in, not just costs to minimize) are the ones that can still move fast ten years later. The ones that didn’t are paying the bill now, whether they call it that or not.
Everything works. And yet something is wrong.
Now you have a name for it.
What symptom do you recognize most in your current work: change amplification, cognitive load, or unknown unknowns? And what’s the smallest strategic investment you could make this week?
References
[1] Ousterhout, J. (2021). A Philosophy of Software Design, 2nd ed. Yaknyam Press.
[2] Cunningham, W. (1992). “The WyCash Portfolio Management System.” OOPSLA ‘92 Experience Reports.
[3] Booking.com Engineering Blog. (2024). “Modernizing a Legacy Endpoint and Why It’s Worth It.” medium.com/booking-com-development
[4] Bosworth, A. “Stable Infra.” boz.com/articles/stable-infra
[5] Henricodolfing.com. (2019). “Case Study 4: The $440 Million Software Error at Knight Capital.” henricodolfing.com
[6] CodeAhoy. (2020). “Tech Debt Developer Survey Results 2020 – Impact on Retention.” codeahoy.com
[7] Jacobs, P. & Schlatmann, B. (2017). “ING’s agile transformation.” McKinsey Quarterly. mckinsey.com
[8] Fowler, M. “SnowflakeServer.” martinfowler.com/bliki/SnowflakeServer.html