The Pragmatic Programmer, 25 years on
Re-reading the 1999 classic in a world of LLM pair-programmers. What held up, what needs a footnote, and what AI didn't replace.
The dog-ear on page 47 was already there when I bought my copy secondhand, sometime around 2012. Someone else had folded it down before me — at the “broken windows” passage, the one that says a single bad piece of code left unfixed signals that the rest of the codebase is fair game for neglect. I don’t know who bent that corner. But I’ve returned to that page more times than I can count.
A junior engineer on my team asked me last month if the book was still worth reading. She’d seen it mentioned in a Hacker News thread. Someone in the comments had written: “It’s fine, but ChatGPT can do everything they describe in the code generation chapter.” She showed me the comment on her phone, slightly unsure whether to agree.
I picked the book up off my shelf that evening. I read it over four nights on the commute home.
Here’s what I found.
What held up
Let me start with what still feels true twenty-five years in, because the list is longer than I expected.
DRY — Don’t Repeat Yourself — still cuts. Hunt and Thomas define it precisely: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” Not “don’t copy-paste” — though that too. The deeper idea is about knowledge. When the same business rule lives in three places, the rule itself is unstable. I’ve watched this exact problem sink two codebases. The principle hasn’t aged at all; the teams I’ve seen ignore it paid exactly the price the book predicts.
Orthogonality is the one I under-applied in my first decade. The book’s framing is elegant: two components are orthogonal if changing one doesn’t require changing the other. In practice, this is the argument for decoupled systems, for clear interfaces, for not letting your authentication logic know anything about your rendering logic. It’s also the reason microservices became so appealing — and why badly-designed microservices are just a distributed ball of mud. The principle is timeless; the execution still trips people up constantly.
Pragmatic paranoia is the section I re-read with the most embarrassment. The book tells you to “design by contract” — to assert what you expect at the boundary of every function, to write code that crashes loudly rather than proceeding silently on bad input. I did not follow this advice for most of my career. I wrote optimistic code because it was faster to write. I paid for it in production incidents where the error was three steps downstream of where the bad data entered. The book was right. I was lazy.
Tracer bullets hold up as a development model, maybe more now than in 1999. The idea: instead of building the whole system layer by layer, fire a thin vertical slice through the architecture first — something that touches every layer but does very little. Get it working end-to-end, then add flesh to the skeleton. This is essentially what a good sprint zero looks like, and it maps cleanly onto how good teams scope proof-of-concepts. The metaphor has aged better than most metaphors in tech books.
The advice to always be learning — keep a “knowledge portfolio,” add one new language per year, read broadly — sounds obvious written out. It didn’t feel obvious in 1999, when the dominant culture was to pick a stack and plant yourself in it. It still doesn’t feel obvious now, watching people who’ve spent five years in one framework treat anything outside it as suspect.
What needs a footnote
This is where the Hacker News commenter had a point, even if they drew the wrong conclusion.
Chapter 7 covers code generators — tools that produce boilerplate from metadata, templates, scripts that write scripts. In 1999, building a code generator was an advanced move, a force multiplier that maybe 10% of teams used intentionally. The book treats it as aspirational advice: you could do this if you were sophisticated enough.
In 2026, every IDE I’ve touched in the last two years ships with a copilot that generates code on demand. The advice to “use code generators” is not wrong — but it’s like telling someone to “consider using electricity.” It’s the baseline, not the edge.
The “learn a new language every year” advice also reads differently now. The original intent was sound: learning a new language forces you to encounter new paradigms, new ways of decomposing problems. A year with Lisp changes how you think about functions. A year with Prolog changes how you think about constraints. I still believe that.
But the practical argument — that knowing multiple languages makes you more employable, more versatile day-to-day — is weaker when a copilot writes serviceable Python from a Ruby developer’s prompt. The surface-level competency arrived for free. The deeper argument, the one about thinking, is unchanged. They just conflated the two.
The estimation heuristics in the book — the rules of thumb for how long tasks take — need the most updating. They were calibrated for a world where “write a new endpoint” meant an engineer alone with their editor. Add an AI pair-programmer that can scaffold the boilerplate in minutes, and the distribution of task times shifts significantly at the lower end. The time you save on structure gets reallocated to review, judgment, and integration. The heuristics aren’t wrong; they’re just measuring a different thing than they used to.
What aged oddly
The 20th Anniversary Edition (2019) is the version worth reading now. Hunt and Thomas went back and replaced specific tool recommendations, updated the examples, retired some sections that had aged into irrelevance. It’s a cleaner read than the original.
Even so, some things sit at an odd angle.
The book’s treatment of “programmer as writer” holds up better than almost any other craft analogy in the literature. They compare software to prose: the same attention to clarity, the same care about what the reader (or future maintainer) will encounter. That framing is still useful. I’ve borrowed it in code reviews.
But the specific writing tools and workflows they gesture toward are artifacts of 1999. There’s an assumption that version control is something you have to argue for, that automated testing is still a debate, that documentation lives in separate files from code. These sections don’t require updating so much as skipping — they’re addressed to a world that doesn’t exist anymore.
What hasn’t vanished is the feeling they were writing against: the programmer who treats software as a craft to be learned carefully versus the programmer who views it as a series of tasks to be dispatched. The book was trying to build a specific kind of mind. That project is timeless.
What LLMs didn’t replace
Here’s what the Hacker News commenter missed.
The book is not really about techniques. It’s about a stance. Hunt and Thomas are describing a way of standing in relation to software — curious, skeptical, empirical, humble. They’re arguing for a programmer who reads their own code like a stranger would, who asks “why does this need to exist?”, who resists both over-engineering and under-engineering because they understand the cost of each.
An LLM pair-programmer can scaffold a REST endpoint in thirty seconds. It cannot tell you whether the endpoint should exist. It cannot look at your system architecture and say: this is an incidental complexity, not an essential one — here’s where the design is lying to you. It cannot decide which broken window to fix first, or which broken window is actually load-bearing and shouldn’t be touched.
Judgment is the thing the book is actually teaching. Taste. The discipline to stop before gold-plating. The courage to throw something away when you’ve built the wrong thing well. The self-awareness to notice when you’re attached to your solution rather than your user’s problem.
None of that arrived in a model weight. It’s still the job.
The move that compounds
Late in the book, Hunt and Thomas state the thesis plainly. A pragmatic programmer, they write, is not dogmatic. They don’t marry a tool, a methodology, or a language. They use what works, discard what doesn’t, and remain genuinely curious about what they haven’t tried yet.
This is a quiet idea. It doesn’t generate conference talks. It doesn’t produce frameworks named after you. But I’ve watched it compound over fifteen years of my own work and in the engineers I’ve seen grow into genuinely excellent practitioners.
The dogmatic programmer’s knowledge gets stale. The tool they love becomes a legacy system. The methodology they committed to falls out of favor. The pragmatic programmer’s way of learning doesn’t stale. It just finds new things to learn.
That meta-skill — the disposition, the habit of mind — is what the book is actually selling. Everything else is examples.
What I tell junior engineers
I still recommend this book. I told my colleague to read it.
What I tell her to skip: most of Chapter 6’s tool recommendations, the appendices, and any section where the examples are about CVS or makefiles. Skip them without guilt.
What I tell her to read slowly: the broken windows passage (page 47, in my copy — but it’s near the front in yours), the orthogonality chapter, the section on “dead programs tell no lies” under pragmatic paranoia, and everything under “Your Knowledge Portfolio.”
Then I tell her to dog-ear the pages that make her uncomfortable. Not the ones she agrees with — the ones she recognizes she’s been ignoring.
Those are the pages worth coming back to. Someone already folded them down for you.