The Pragmatic Programmer · David Thomas & Andrew Hunt
“Care about your craft. Why spend your life developing software unless you care about doing it well?”
Why I Picked This Up
I read The Pragmatic Programmer for the first time as a mid-level engineer who’d just shipped a system that worked but felt wrong. The code passed tests, met requirements, handled edge cases — and was still a nightmare to change. I couldn’t articulate why. I knew something was off the way you know a room is cold before you check the thermostat. This book gave me the vocabulary. More than that, it gave me a philosophy of engineering that transcends any specific language, framework, or era. I’ve re-read it twice since — once when I was moving from senior to staff level, and once when I started mentoring others and realized I needed to articulate the principles I’d been operating on instinctively. The 20th Anniversary Edition updated many of the examples, but the core ideas are largely the same as the original 1999 publication. The fact that a book about software can remain relevant for over two decades tells you something about the quality of its abstractions.Lessons That Aged Beautifully
The Broken Window Theory
“Don’t leave ‘broken windows’ (bad designs, wrong decisions, or poor code) unrepaired.”One small piece of neglect signals that nobody cares — and invites more neglect. A single
// TODO: fix this later that never gets fixed becomes five, then twenty, then a codebase where tech debt is the norm and quality is the exception.
How I apply this: When I join a new codebase or team, the first thing I look at isn’t the architecture diagram. It’s the TODOs, the suppressed linter warnings, the commented-out code blocks. They tell you the team’s relationship with quality better than any process document.
I don’t fix every broken window immediately — that would be naive. But I fix one. Visibly. In my first week. It signals that someone cares, and it’s remarkable how often that single act changes the team’s behavior.
Stone Soup and Boiled Frogs
The parable of stone soup: soldiers trick villagers into contributing ingredients to a “stone soup” by starting with just water and a stone. Each villager adds something small, and together they create a feast. How I apply this: When I want to drive a large technical initiative — say, adopting a new testing strategy or migrating to a better CI pipeline — I don’t write a 40-page proposal and ask for buy-in. I start small. I demonstrate the value in one team, one service, one pipeline. When people see it working, they volunteer their own ingredients. The initiative builds momentum organically instead of requiring top-down mandates. The boiled frog is the inverse: gradual degradation that nobody notices until it’s catastrophic. The deploy pipeline that gets 30 seconds slower each month. The test suite that adds one flaky test per sprint. The oncall rotation that absorbs one more service per quarter. Each increment is tolerable. The cumulative effect is not. My countermeasure: I track leading indicators, not just outcomes. Deploy time over weeks. Test flakiness rate over sprints. Oncall page count per engineer per month. The trend lines catch the boiling before it burns.Good Enough Software
“Great software today is often preferable to perfect software tomorrow.”This one is misunderstood as “ship garbage.” It’s the opposite. “Good enough” is a quality bar — it means the software meets its users’ requirements, is maintainable, and handles failure gracefully. It does NOT mean cutting corners. It means knowing when to stop polishing. How I apply this: In code reviews, I’ve learned to distinguish between “this could be better” and “this needs to be better.” A function that could be 10% more elegant but works correctly, is readable, and is well-tested? Ship it. The marginal improvement isn’t worth the delay. A function with unclear error handling in a payment flow? That needs to be better. The context determines the quality bar. I’ve seen more projects fail from over-engineering than under-engineering. The team that spends three months building an abstraction layer “in case we need it” instead of shipping the feature that validates whether the product is viable? That’s not craftsmanship. That’s avoidance disguised as quality.
Tracer Bullets
“Use tracer bullets to find the target. Tracer bullets work because they operate in the same environment and under the same constraints as the real bullets.”A tracer bullet is a thin, end-to-end implementation that touches every layer of the system but doesn’t implement full functionality at any layer. It’s not a prototype (which gets thrown away) — it’s a skeleton that gets fleshed out. How I apply this: When starting a new feature that spans frontend, API, database, and external services, I build the tracer first. A single happy-path request that flows through every layer with minimal logic at each point. This validates the architecture, surfaces integration issues early, and gives the team a working spine to build on concurrently. This is probably the technique from this book I use most frequently. It’s saved me from the pattern where three teams build their layers in isolation for six weeks and then spend four weeks making them work together in integration hell.
Rubber Ducking
Explaining your problem to an inanimate object (or a patient colleague) to force yourself to articulate the issue clearly — which often reveals the solution. How I apply this: I rubber duck constantly, but my “duck” has evolved. Early in my career, it was a literal stuffed animal on my desk. Now it’s a structured practice: before I ask a colleague for help with a bug, I write up the problem as if I’m filing a support ticket. Context, what I tried, what I expected, what happened instead. About 60% of the time, writing the ticket surfaces the answer and I never need to send it. This is also why I’m a proponent of detailed PR descriptions. Writing the description IS the rubber duck session for the entire change.Lessons That Feel Dated
Not all of the book has aged equally. Code generators and metalanguage: The chapters on code generators and domain-specific languages were relevant in 1999 when writing your own parser was a reasonable Tuesday activity. Today, the spirit of the advice (automate repetitive code production) is valid, but the specific techniques feel like a different era. Text manipulation: The emphasis on mastering text manipulation tools (sed, awk, etc.) was crucial when text was the primary interface. Today, structured data, APIs, and IDEs have replaced a lot of raw text wrangling. I still use these tools, but they’re no longer the daily bread they once were. The “plain text” obsession: Hunt and Thomas advocate for plain text as the universal format. In 2024, this is partially right (configuration, documentation, version control) and partially dated (binary formats, protocol buffers, and structured data stores have legitimate advantages for many use cases).DRY — The Most Misunderstood Principle
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”DRY stands for Don’t Repeat Yourself, and it’s the most cited and most misapplied principle in the book. Engineers read “don’t repeat yourself” and interpret it as “never write similar-looking code twice.” This leads to premature abstractions, god-utility classes, and inheritance hierarchies that make the code harder to understand, not easier. What DRY actually means: Don’t duplicate knowledge. If the same business rule is encoded in two places and they can drift out of sync, that’s a DRY violation. If two functions happen to have similar code but represent different concepts that might evolve independently, that’s not a DRY violation — that’s coincidental similarity. How I apply this:
| Situation | Is it a DRY violation? | What I do |
|---|---|---|
| Same validation logic in API and frontend | Yes — one rule, two representations | Extract to a shared schema (e.g., Zod) |
| Two services with similar retry logic | Maybe — depends on context | Keep separate unless retry semantics are guaranteed to be identical forever |
| Two React components with similar JSX | Usually no — different concepts | Keep separate; extract shared styles if any |
| Same SQL query in two endpoints | Yes — one query, two locations | Extract to a repository/data access layer |
The “Rule of Three” is a good guardrail: tolerate duplication until you see the same pattern three times. By the third occurrence, you understand the actual abstraction. Before that, you’re guessing — and wrong abstractions are worse than duplication.
Orthogonality in the Microservices Era
“Eliminate effects between unrelated things.”Orthogonality means that changing one thing doesn’t affect unrelated things. It’s the principle behind separation of concerns, modular design, and — in the modern era — microservices. The irony: Microservices were supposed to give us orthogonality. Each service owns its domain, deploys independently, and changes without affecting others. In practice, distributed systems often create new coupling through shared databases, synchronous API chains, and implicit contracts in event schemas. How I apply orthogonality today:
- At the code level: Functions should do one thing. Modules should own one concept. A change to authentication shouldn’t require a change to billing. This hasn’t changed since 1999.
- At the service level: I evaluate coupling by asking “Can this service deploy independently without coordinating with any other team?” If the answer is no, the services aren’t truly orthogonal — they’re a distributed monolith.
- At the team level: Conway’s Law tells us that system architecture mirrors organizational structure. Orthogonal systems require orthogonal teams. If two teams share a codebase, a database, or a deploy pipeline, their services are coupled regardless of how the architecture diagram looks.
The “Pragmatic” Mindset as Career Philosophy
The book’s deepest lesson isn’t any single technique — it’s the mindset behind the title. Pragmatism means making decisions based on practical consequences rather than theoretical purity. What this means in practice:- Pick the right tool, not the trendy tool. If a boring, well-understood technology solves your problem, use it. You don’t get points for difficulty.
- Good judgment comes from experience, and experience comes from bad judgment. Every mistake is data. The engineer who’s never broken production is the engineer who’s never shipped anything risky enough to matter.
- Invest in your knowledge portfolio. Hunt and Thomas compare your skills to a financial portfolio — diversify, invest regularly, rebalance periodically. I dedicate at least 5 hours per week to learning something outside my immediate job. Some of it pays off immediately. Some of it pays off years later. All of it compounds.
- Be a catalyst for change. Don’t ask for permission to improve things. Start the stone soup. Show what’s possible in a small scope and let momentum do the persuading.
What Changed After Reading This
- I started keeping an estimation log — tracking predictions vs outcomes, which revealed my systematic overconfidence. The book’s advice to “keep a knowledge portfolio” extended to tracking my own judgment.
- I adopted tracer bullets as my default approach for any cross-cutting feature work.
- I stopped reflexively DRYing up code and learned to tolerate duplication until the real abstraction reveals itself.
- I started fixing broken windows as a leadership practice, not just a coding habit.
- I embraced “good enough” as a quality bar rather than an excuse — and got better at calibrating where the bar should be for different contexts.
Key Quotes I Revisit
- “It’s not about tools, it’s about principles.”
- “You can’t write perfect software. Did that hurt? It shouldn’t.”
- “Don’t live with broken windows.”
- “Program close to the problem domain.”
- “Critically analyze what you read and hear.”
Who Should Read This
Every engineer in their first five years. Every senior engineer who feels like they’re running on instinct and wants to articulate their principles. Every tech lead who’s responsible for code quality and team culture but isn’t sure how to teach “taste.” Read it with a highlighter. Better yet, read it with a team — one chapter per week, discussion over lunch. The ideas compound faster when they’re shared.Pairs well with: Thinking, Fast and Slow for the cognitive biases that undermine pragmatic thinking, Staff Engineer for applying these principles at the leadership level, and Building a Second Brain for maintaining the knowledge portfolio Hunt and Thomas recommend.
