DEV Community

Cover image for Beyond Barnum: crafting meaningful requirements in System Design
Thomas de Saint Exupery for Doctolib

Posted on • Originally published at Medium

Beyond Barnum: crafting meaningful requirements in System Design

Make it scalable, but not too complex. And of course, everything needs to be secured and compliant.

— Every System Design Ever

If you've ever participated in a system design interview – whether as an interviewer or candidate – these words probably sound familiar. They're the software engineering equivalent of a horoscope: vague enough to apply to any system, yet specific enough to feel meaningful. Just as horoscopes tell you that you're "sometimes outgoing but also enjoy quiet time alone" (who doesn't?), these requirements seem to provide guidance while actually saying very little.

Welcome to the Barnum effect in system design.

As a staff engineer at Doctolib who has been conducting system design interviews for over a year, I've seen this pattern repeat itself: candidates nod sagely at these generic requirements, only to struggle when it comes to making concrete design decisions.

But here's the thing: real systems aren't built on truisms. You can't tell your infrastructure team to make it "not too complex" or ask your security team to just make “everything secure." These Barnum statements are the comfort food of system design – they feel good but provide little nutritional value for your actual engineering decisions.

The anatomy of void requirements

Make it scalable

What's our growth projection? Are we expanding to new countries? Each territory brings its own complexity: different currencies, tax systems, time zones, etc. There are many other dimensions of scalability often overlooked: data volume growth over time, number of supported third-party integrations, variety of client applications or even organizational scalability as teams grow and specialize. These dimensions radically change our technical choices.

but not too complex,

What does "complex" mean in your context? Sometimes, a "complex" solution might be exactly what's needed to solve a complex problem. Also "Not too" is a classic example of a truism - a statement that's so obviously true it adds no value to the discussion.

And of course, everything needs to be secured,

What threats are we protecting against, and what's the actual impact of a breach? Overemphasizing security can actively harm user experience or system maintainability. Like "not too complex", this is another truism - no one would argue for an unsecured system, making the requirement meaningless without specifics.

and compliant.

Compliant with what regulations, in which jurisdictions, and for what type of data? The choice between AWS, Azure, or on-premise hosting could hinge entirely on specific compliance requirements. This requirement often appears as a checkbox item, ignoring that compliance is a complex spectrum of legal, technical, and operational considerations.

From void to valuable: four principles for better requirements

After reading these Barnum-like requirements, you might be wondering "How do I write better ones?" Here's a confession: reviewing my own system designs from the past two years, I discovered I had fallen into the same trap. I sometimes used these vague requirements to subtly steer architecture decisions in the direction I preferred. To protect myself from this bias and improve the quality of my designs, I strictly follow these principles:

  1. 🎯 Focus on outcomes, not solutions
    • Define what success looks like rather than prescribing how to achieve it.
  2. 🧪 Requirements must be testable
    • If you can't test it, you can't verify it, and therefore it's not a requirement.
  3. ↔️ Use ranges and degrees instead of absolutes
    • Express constraints through both ranges and precise language (must/should/may) to communicate flexibility and priorities.
  4. ⚖️ Define necessary and sufficient conditions
    • Understand both the minimum needed to succeed and when additional effort yields no value.

To illustrate them, I'll share conversations between two engineers - one proposing requirements, let's call them Pat, the other - Sam - helping to refine them. You might recognize yourself in either role: I know I've played both parts and I’ll continue. That’s why I need to be challenged by others to cover my blind spots.

Principle 1: Focus on outcomes, not solutions 🎯

Crafting requirements

P – The system must be scalable.<br>
S – Actually, scalability is a category of requirements, not a requirement itself.

P – Okay... so what kind of requirements then?<br>
S – What outcomes do you need? How many users? Which regions? How many devs?

P – Well, we’re in France, with plans to expand to Germany next year. And our dev team will grow from 5 to 20 people.<br>
S – Now that's something we can work with!

Software engineering literature often distinguishes between functional and non-functional requirements. But the real insight isn't in sorting requirements into these categories - it's understanding their origins and purposes.

Functional requirements emerge from business outcomes: they describe what problems we need to solve. They come from product vision, user problems, and business goals. "Doctors can view their schedule offline" is a clear outcome we can build for.

Non-functional requirements are different - they're rarely explicitly requested but always implicitly expected. As technical experts, it's our responsibility to identify, propose, and quantify them. When a product owner says "doctors should view their schedule," they might not specify "with less than 200ms latency" - but we know that slow response times would make the feature useless. These requirements help us choose between technically viable solutions and find the one that best serves our users.

These non-functional requirements serve as a mood board for technical quality: security, maintainability, observability, and many more. They represent our professional standards rather than specific features.

Requirements translate desired outcomes into actionable information that helps us evaluate and choose between possible solutions. Each solution comes with its own trade-offs, but we can only assess these trade-offs meaningfully when we clearly understand what we're trying to achieve.

For example, "support expansion to 3 new countries" might lead us to consider:

  • Multi-region deployment
  • Data residency solutions
  • Internationalization approaches.

The hidden solution trap

Later that day…<br>
P – Also, the system must support independent runtimes.<br>
S – And there you have it \- you've fallen into the hidden trap of disguising a solution as a requirement.

⚠️ Sometimes we already have a solution in mind and work backwards to write requirements that will inevitably lead to that solution.

❌ Once I wrote: "The system must maintain strict consistency across all operations", thinking: "Let’s use PostgreSQL".
✅ Real requirement: "Users must never see outdated prices during checkout"

❌ another time, I wrote: "The system must support independent deployment of components", thinking: "I want microservices".
✅ Real requirement: "Teams must be able to release features independently"

Requirements should create space for architectural discussions, not circumvent them. When we find ourselves writing requirements that can only be satisfied by one specific solution, it's often a sign we're working backwards from a preferred technology rather than forward from actual problems.

Principle 2: Requirements must be testable 🧪

P – Okay “not too complex” means nothing, how about “must be user-friendly”?<br>
S – Let's use another tool for this one, can you test user-friendly?

P – I guess... if practitioners can easily upload their documents without getting frustrated.<br>
S – Now we're getting somewhere !

S – We could say '95% of practitioners successfully upload their first document within 2 minutes without assistance.'<br>
P – That's something we can actually measure!

This is a fundamental truth in software engineering: if you can't test it, you can't verify it. And if you can't verify it, it's not really a requirement - it's a wish.

From untestable to testable:

  • ❌ Don't : The code should be maintainable
  • ✅ Do : A new developer should make their first production commit within their first week

  • ❌ Don't : The application should handle high load ->

  • ✅ Do : The system maintains response times under 200ms for the 99th percentile while processing 100 concurrent requests

Notice how testable requirements naturally become more specific and actionable. They create clear acceptance criteria and enable objective discussions about whether they've been met.

By the way, even architectural decisions can be tested - as Neal Ford describes in Software Architecture: The Hard Parts, fitness functions help us verify our architectural characteristics through automated tests.

Principle 3: Use ranges and degrees instead of absolutes ↔️

P – For the document upload feature, everything must be secure.<br>
S – Must? Are you sure about that word choice?

P – Well... isn't security always mandatory?<br>
S – Let's break it down. What aspects of security are truly mandatory versus highly desirable?

P – Okay... patient documents MUST be encrypted. The upload link SHOULD expire after 24 hours. And we MAY add watermarking for extra tracking.

In software engineering, precision in language helps communicate constraints and priorities. That's why we use specific terms:

  • 🔒 MUST: Absolute requirement, non-negotiable
  • 📌 SHOULD: Highly desirable but not mandatory
  • ✨ MAY: Optional, nice to have

P – Ah, and we'll have to validate documents with the government, they have an API that takes a second.<br>
S – 1 second every time? What's your confidence here? Maybe you can use tolerances?

P – Well... their documentation says 1 second, but that's probably best-case scenario.<br>
S – A controlled API might give you 1s ± 50ms. But for a government service, we're probably looking at 1-2s under load.

P – That's quite different! One is precision, the other is uncertainty.<br>
S – Exactly! Be explicit about this uncertainty, it helps us make better design decisions.

Just like mechanical engineering uses tolerances, software requirements work better with realistic ranges.
They not only acknowledge that perfect precision is neither possible nor necessary but also help us express our level of confidence - especially crucial when dealing with external dependencies.

For example, when specifying API response times:

  • In-memory search must return within 5ms ± 1ms (precise, highly controlled)
  • Full-text search should take 0.5-2s (moderate variability, Historical data available)
  • System should serve 10k-100k users (Low Confidence for new Market Entry Based on comparable markets)

Principle 4: Define necessary and sufficient conditions ⚖️

P – For the document upload service, how do we figure out what availability we provide?<br>
S – Let's find our boundaries. What's the minimum that keeps doctors using the service?

P – Well, at 99.9%, that's 43 minutes of downtime per month...<br>
S – During business hours, that's already one visible incident. Any more would damage trust.

P – So that's our necessary condition \- can't go lower.<br>
S – Right. Now, 99.999% means 26 seconds downtime per month, and 99.9999% is just 2.6 seconds.

P – Doctors won't notice the difference. So 99,999% is sufficient.

This dialogue illustrates a key principle: requirements have both a lower and upper bound. While necessary conditions are rarely missed (failure makes them obvious), sufficient conditions are often overlooked - leading to wasted optimization efforts.

✔️ Necessary conditions define the minimum viable threshold - below this, the solution fails its purpose. This creates a clear "MUST" requirement that helps eliminate unviable solutions early. For example, if document upload takes more than 5 seconds, users won't use it, making any solution that can't meet this threshold unviable.

✅ Sufficient conditions identify the point beyond which additional improvement has no impact on users or business outcomes. It's not about trade-offs - if users can't perceive a difference, any additional optimization is objectively worthless, regardless of how easy or hard it might be to achieve. I want to insist on this. The sufficient condition isn't a compromise - it's the point where further improvements become meaningless.

  • When displaying product inventory in e-commerce
    • ✅ Sufficient conditions : 5-second consistency delay
    • Users take longer than this to add items to cart, faster synchronization creates no benefit
  • When displaying search results
    • ✅ Sufficient conditions : 100 results per page
    • Analytics show users never scroll past result 80
  • When processing expense claims
    • ✅ 48-hour validation delay
    • Payroll cycles operate monthly, faster processing doesn't accelerate reimbursement

Between these boundaries lies our practical engineering target. Understanding this helps prevent the common pitfall of over-engineering in pursuit of unnecessary perfection.

Conclusion

Requirements aren't just a box to check in your system design process - they're the foundation that enables and justifies rational technical decisions. They serve a dual purpose: first guiding our design choices, then later justifying these decisions to others.
We didn't choose Redis because it was trendy, but because we needed sub-millisecond access to session data. We didn't adopt Kubernetes because everyone else did, but because our deployment requirements demanded independent scaling of components. These requirements become the rational foundation of our architecture - not "because the tech lead said so" but because specific, measurable criteria led us there.

🎁 My Go-to Checklist

As a gift for reading to the end, here's what I keep on my mental sticky note:

  • When you hear "we need X" → ask "what problem are we solving?"
  • When requirements feel vague → "how would we test this?"
  • When unsure about numbers → start with ranges
  • When optimizing → check if you're past "good enough"
  • When justifying decisions → point to requirements, not opinions

Keep these in your back pocket - they've saved me from many circular architecture discussions!

Top comments (0)