How many developers who start reading The Pragmatic Programmer actually finish it? Speaking with engineers across the industry, I often hear a familiar story: people put it down after 20 pages. Some assume a book written decades ago isn't relevant to today's fast-changing tech stack, while others might feel they have enough years of experience under their belt and are anyways too busy to revisit concepts that are 20 years old.
Yet in an era dominated by AI code generation, instant search, and rapidly shifting frameworks – dismissing these books misses their core purpose. Books like The Pragmatic Programmer don't exist to teach you temporary framework syntax or ephemeral APIs, they teach you how to think, how to build mental abstractions, and how to navigate problem spaces before writing a single line of code.
In this post, we will highlight several key takeaways from the book – concepts which are often overlooked or undervalued. We will explore the importance of domain knowledge, collaboration, and how these disciplines reinforce one another.
Don’t Do What the Customer Wants – Do What They Need
One of the core principles of the Agile Manifesto is satisfying the customer through early and continuous delivery. The idea is simple: incremental delivery, gathering feedback, and course-correct. But continuous delivery only works if you are steering in the right direction. Before you even write the first user story, a subtle yet critical distinction must be made: don’t build what the customer asks for, build what they actually need. Without questioning the core objective up front, Agile increments just turn into a faster way of building the wrong thing.
To understand what customers need, developers must cultivate domain knowledge rather than being siloed from the product. Shielding developers from business and product decisions creates a negative feedback loop:

Without domain context, developers naturally find purpose in technological complexity rather than solving user problems. Domain knowledge is an engineer's strongest lever for delivering impactful software.
Navigating "The Requirements Pit"
The Pragmatic Programmer suggests that requirements are a continuous process of discovery, not a static document handed down from above. In reality, clients rarely truly know what they want at the start – they usually only have an incomplete picture.
Consider a classic example from the book: a client asks for "Free shipping on orders of $50 or more." A developer rushing straight into code might think that it is an easy task. First, you check the total cost of the cart and then you apply for free shipping if the price exceeds $50. However stopping to think reveals missing business logic: Does this apply before or after taxes? What about digital vs. physical goods? International orders? How frequently will this threshold change?
Requirements must be actively uncovered through questioning, domain research, and tight iteration.
We faced a similar struggle with ambiguity in our own product development at ComplyAdvantage, where uncovering the actual need proved more valuable than implementing the initial request.
A Real-World Example: Iterating Toward the True Problem
At ComplyAdvantage, our pipelines continuously monitor millions of entities across thousands of risk sources (sanction, politically exposed person a.k.a PEP, adverse media etc.) for something called a net new risk. Simply put, our system allows our clients to answer the question: has anything changed in a customer’s risk profile since the last time the customer was verified?
Clients receive alerts when there has been a relevant change in a customer's risk profile. At the scale of data we process, even a tiny fraction of false positive alerts results in thousands of noisy notifications that compliance analysts must review manually. The manual review is an actual legal requirement, once a monitoring alert is fired, an actual human must be looped in.
To reduce the amount of possible alerts, a downstream team approached us with a very reasonable request: "Would it be possible for us to receive more granular information about what changed between updates to the same entity and its related risk signals?”
On paper, simply adding more information to the already existing delta payload (what changed in this entity since last time it was processed?) looked like the obvious answer. But applying a pragmatic mindset made us to pause, gather domain knowledge, and iterate before writing a single line of code:
- Anchoring the Brainstorm with a Raw Draft: Instead of spending weeks writing an exhaustive 10-page design document, we put together a rapid, minimal draft of initial requirements. For instance, we categorised data tracks by risk type, recognising that high-priority data like sanctioned individuals and PEPs required different handling than individuals mentioned in an adverse media article. This raw draft took minimal effort, but it provided a tangible anchor for early cross-team discussions.
- Gathering Domain Knowledge & Uncovering Nuances: As we met as a team to uncover the unknowns behind the initial request, we learnt the existence of critical compliance nuances. For example, in France, an individual is no longer considered a PEP five years after leaving office, whereas in other jurisdictions, PEP status applies for life. The initial idea of simply extending the existing static delta payload alone wouldn’t allow clients to configure their alerts to capture these temporal state changes over time!
- Refining the Scope: Through the iterative feedback loops between product and engineering, we reached a key realisation: deltas only describe what changed between version A and version B. They don't give clients the ability to decide for themselves on the temporal validity of the risk, customisable on the legislation of their compliance duties.
By grounding our work in domain research and collaborative iteration, we ended up expanding how our data was structured across services by prioritising the tracks that delivered immediate, high-impact value to clients: the ability to build bespoke alerts to detect changes to risk profiles based on their own temporal definition of risk.
Engineering as a Collaborative Discipline
Defining requirements is where a developer's job begins, not where it ends. Turning ambiguous problems into resilient software relies on core collaborative practices. The Pragmatic Programmer places a strong emphasis on collaboration, and that is something that never goes out of style:
- Iterative Refinement: Start with a raw draft to anchor discussions, validate assumptions with domain experts, and adapt your approach before committing to code.
- Peer Consultations: Pair and mob design sessions aren't a sign of hesitation, they are essential architectural tools for unearthing edge cases early.
- Explaining to Non-Experts: Explaining a complex problem to a colleague outside your domain forces you to structure your thoughts clearly, often revealing hidden assumptions in the process.
Syntax, AI tools, and frameworks will keep evolving at breakneck speed. But the core skills of software engineering, such as domain curiosity, structured thinking, and collaborative problem-solving, will remain always relevant. That is why opening those "old" books remains one of the best career investments that you can make – even in the age of AI.
