← all pages

guide · 2026-01-27

Working Code is Not Enough

When tools change, the principles stay the same. LLMs don’t remove the need for strategic design.

“The Year I didn’t write code”

It’s 2026, and everywhere I look, people are declaring that software engineering has changed forever. That they’re no longer writing their own code. The core argument is something like:

Software is just a means to an end, and always has been. If it works, it’s good code.

The claim sounds pragmatic until you’re the one fixing a bug at 3 AM.

Working code is not the end of software engineering; it’s the start of its long-term investment and cost. If software is meant to last, then working code is only the first requirement.

Time changes everything in software. As systems age, hard questions arise. The answers determine whether software remains usable at all. Your code can work without thinking about these questions, but there are consequences. Those consequences tend to show up as a familiar set of challenges:

Tactical vs Strategic

There’s an academic term for the “just make it work” programming mindset: tactical programming, coined by John Ousterhout in his book A Philosophy of Software Design.

Tactical programming optimizes for working code at the expense of global simplicity. Each shortcut adds complexity. Over time, that complexity compounds into technical debt, slowing future changes and increasing risk.

The opposite is strategic programming: taking the time to design systems that are easier to understand, modify, and extend.

This isn’t just a theory. In a 2021 study, across six countries, developers cited time pressure and deadlines as the most common cause of technical debt.1 These pressures provoke a tactical mindset, and the debt follows.

Vibe Coding as Tactical Programming at Scale

Vibe coding is the most efficient tactical programming tool we’ve ever had. LLMs didn’t invent tactical programming. They removed its friction.

Vibe coding delegates the work of writing code to an LLM-backed coding agent. You prompt it, and you get back code. Personally, I find them addictive—especially for getting through the list of one-off scripts and small apps that I always wanted to build and didn’t have the time to just type the code. That’s why vibe coding is so popular: the output speed can be faster compared to what a person can type and sometimes that code is good enough.

However, in non-trivial systems, coding was almost never the bottleneck; the real challenge has always been aligning on the right outcomes and maintaining long-term quality.

Here’s my simplified view of the development process. This loop matters because it forces teams to align on three outcomes: solving the right problem, enabling future change, and ensuring the software actually works in the real world.

The original post included an illustration of this development process; the image can be added when the source asset is available.

I won’t dive into each here, but in my experience skipping these steps doesn’t just risk bugs; it risks building the wrong thing or making future change difficult and prohibitively expensive.

As of this writing, vibe coding has become more than a meme and publicly there are high-profile companies and tech influencers claiming they have stopped writing X% of code themselves. Some review the code, but others ship LLM-generated code with minimal oversight.

Vibe coders follow a shorter loop that seems to primarily focus on skipping alignment and reviews:

Prompt

Smoke Test it

Ship it

Monitor it

What’s missing from the vibe loop is intentionality. The philosophy seems to be that if it doesn’t work, you can go back and fix it later.

This way of thinking is not new. I’ve seen teams ship fast with a “move fast and break things” mentality, then spend months untangling what they built when they needed to add a feature or fix a bug. Vibe coding accelerates this process; it doesn’t improve it. Vibe coding offers the same trade-offs as before, accelerated.

Meta popularized the term “move fast and break things” and later abandoned this mentality—but they didn’t abandon speed. They learned that speed without stability eventually makes you slower. In a 2014 interview with Wired magazine, Mark Zuckerberg stated that their motto had changed to:

“Move fast with stable infrastructure…Because when you build something [such] that you don’t have to fix it 10 times, you can move forward on top of what you’ve built.”2

What’s the problem?

The danger isn’t using tactical programming; it’s drifting into it by default. Defaults shape systems more than intentions do. Knowing when to switch between tactical and strategic modes is what separates sustainable software from spaghetti code.

When Tactical Makes Sense

When Strategic Matters

Takeaways

Ask which decisions you’re accelerating and which you are skipping. In the end, the only agency we have is in the decisions we make. Sustainable software is the cumulative effect of making the right decisions more often than not.

In future posts, I’ll be sharing more about ways to think about these decisions, starting with lessons and red flags from Ousterhout’s A Philosophy of Software Design.

Footnotes

  1. Source reference from the original Substack post; citation details to be restored.

  2. Mark Zuckerberg, 2014 Wired interview; source link to be restored.