Constraints & Trade-offs
When Your Team Disagrees (And Why They’re Both Right)
Picture this: your startup’s engineering team is in a heated debate. One engineer argues passionately for PostgreSQL—reliable, ACID-compliant, battle-tested. Another insists on MongoDB—scalable, flexible, perfect for their document model. The CEO mentions cost concerns. Your DevOps lead worries about operational complexity. Nobody is wrong. They’re all looking at the same problem through different lenses, each prioritizing different constraints.
This is the heart of system design. Unlike mathematics, where solutions are often definitively right or wrong, distributed systems force us into a world of carefully managed trade-offs. You cannot have everything. You can have almost anything, but the moment you choose something, you’ve implicitly chosen against something else.
In this section, you’ll learn to identify constraints systematically, understand the fundamental trade-offs that shape every system, and make intentional decisions rather than stumbling into them. We’ll build on everything you’ve learned so far about stakeholders, requirements, and scale—because constraints are where all those abstract needs collide with reality.
The Categories of Constraints That Actually Matter
A constraint is anything that limits your design choices. Think of it as a fence around your solution space. Within that fence, you have freedom. Outside it, your solution breaks.
Technical constraints come from what’s physically or mathematically possible. A relational database can’t easily shard across regions without violating ACID properties at scale. JavaScript can’t access a user’s filesystem without explicit permission. These are laws of the land—not arbitrary, but fundamental.
Business constraints are about money, time, and priorities. You might need the system live in three months, not three years. Your infrastructure budget is fixed. Your team has expertise in Go but not Rust. These constraints often shape designs more than we admit, and that’s okay—pretending you have unlimited resources just wastes everyone’s time.
Regulatory constraints come from laws and compliance requirements. Financial systems need audit trails. Healthcare systems must comply with HIPAA. European services must respect GDPR. These aren’t optional, and they often determine architecture (PCI DSS requirements drive how payment systems are isolated, for example).
Team constraints are about your people. You can’t use a technology nobody knows. Onboarding takes time. Turnover means you lose domain knowledge. A constraint of “two engineers, both full-stack” shapes designs very differently than “ten distributed systems specialists.”
Now, what’s a trade-off? It’s the consequence of making a choice. You chose PostgreSQL, so you’re trading away the schema flexibility of MongoDB. You chose to put everything in one region, so you’re trading away the disaster recovery benefits of geographic distribution. Trade-offs aren’t failures—they’re inevitable. Your job is making them intentionally.
The CAP Theorem: Why You Can’t Have It All
You’ve probably heard of CAP theorem. It states that a distributed system can guarantee at most two of these three properties:
- Consistency: Every read returns the most recent write
- Availability: Every request gets a response (not an error)
- Partition tolerance: The system survives network failures
Here’s what matters: when a network partition happens (and it will), you have to choose. You can’t have all three. You either:
- Choose CP (consistency and partition tolerance): sacrifice availability. When nodes can’t talk, some requests fail. Think: financial transactions where correctness is non-negotiable.
- Choose AP (availability and partition tolerance): sacrifice consistency. Requests always work, but you might read stale data. Think: social media feeds where approximate data is fine.
- Choose CA (consistency and availability): sacrifice partition tolerance. This basically means “perfect network, never down.” Only works for single-node systems or perfectly reliable networks (which don’t exist at scale).
PACELC extends this: If there’s a partition, you choose consistency or availability. Else (no partition), you trade off latency and consistency. Even with perfect networks, you’re choosing between fast-but-possibly-stale (low latency) or slow-but-accurate (high consistency).
This isn’t theoretical. It’s your system’s bloodstream. Understanding it changes how you design.
Every Design Decision Is a Weighing Scale
Let me show you the core trade-offs you’ll encounter again and again:
| Trade-off | Side A | Side B | When You Choose A | When You Choose B |
|---|---|---|---|---|
| Consistency vs Availability | Strong consistency | High availability | Financial systems, inventory | Social media, analytics |
| Latency vs Throughput | Low latency | High throughput | Real-time trading | Batch processing, analytics |
| Cost vs Performance | Cheap infrastructure | Fast systems | Startups, MVPs | Demand-driven performance |
| Simplicity vs Flexibility | Single tool, simple ops | Pluggable, custom solutions | Early-stage products | Mature, evolving systems |
| Replication vs Consistency | Fast reads (many copies) | Single source of truth | Caching, CDNs | Transactional databases |
Notice something? None of these have a “universally right” answer. The correct choice depends on your constraints and requirements.
Pro tip: The best architects don’t optimize for the one dimension—they optimize for the constraint that hurts most. If your users are leaving because of latency, optimize latency and accept some stale data. If you’re losing money on infrastructure, optimize costs even if performance takes a hit.
How to Identify Your Constraints Systematically
Don’t just assume. Document them. Here’s how:
Step 1: Gather stakeholder constraints. Talk to your business folks, your team, your legal team. Make a list. “We have six months,” “we can spend $50k/month on cloud,” “we need HIPAA compliance,” “we only know Python”—write them down. This isn’t a limitation, it’s clarity.
Step 2: Identify technical prerequisites. Do you need global distribution? Sub-second response times? Must you store sensitive data in specific geographic regions? These come from requirements, scaled by your constraints.
Step 3: Build a decision matrix. List your candidate solutions. Score each against your constraints. Don’t overthink it—1 to 5, with explanations. A solution that violates a hard constraint gets a 1. A solution that exceeds a requirement might also get a 1 (over-engineering).
Here’s a real example: choosing a database for an e-commerce platform. You need ACID transactions (consistency matters for inventory), sub-100ms writes, and geographic distribution. PostgreSQL is single-master (hard distribution limit). MongoDB offers eventual consistency (risky for inventory). DynamoDB offers global tables with eventual consistency. CockroachDB offers global distribution with strong consistency.
Database Options | ACID | Latency | Distribution | Ops Complexity | Cost
PostgreSQL | 5 | 5 | 1 | 2 | 3
MongoDB | 2 | 4 | 3 | 3 | 3
DynamoDB | 2 | 4 | 5 | 5 | 4
CockroachDB | 5 | 3 | 5 | 2 | 2
(These are illustrative. Your scoring depends on your specific constraints.)
Step 4: Document trade-offs explicitly. For every choice, write: “We chose X because of constraints C1, C2. This means we’re accepting trade-off T (e.g., higher latency, complex operations). If constraint C3 becomes critical, we’d reconsider.”
This isn’t documentation for bureaucracy. It’s documentation for the future. Eighteen months from now, when someone asks “why didn’t we just use Kafka?”, you can show them the constraints of that time, and whether they’ve changed.
One Engineer’s Consistency Model Is Another’s Nightmare
Consistency models are where trade-offs get concrete. When you write data, and someone else reads it immediately, what do they see?
Strong consistency (linearizability): They see your write. Always. It’s the intuitive model, but enforcing it across geographies costs latency—you have to coordinate. Example: database transactions with ACID guarantees.
Eventual consistency: They might see the old value initially, but eventually, they’ll see your write. It’s fast because you don’t coordinate immediately. Example: DNS propagation, or a social media like count.
Causal consistency: A middle ground. If event B depends on event A, observers see them in that order. But unrelated events can be out of order. Example: chat messages in a thread should be ordered, but messages in different threads can be eventually consistent.
Read-your-own-writes: You always see your own writes immediately, but others might see eventual consistency. Example: Gmail—you always see your sent email immediately, even if it takes a few seconds to reach the recipient.
None of these is “better.” They’re different tools. Financial transactions demand strong consistency. Social media feeds tolerate eventual. Chat needs causal. Understanding these models means you’re not just picking a database—you’re consciously choosing a consistency guarantee and accepting its consequences.
The Latency-Throughput Seesaw
Here’s another trade-off that feels like a physical law: latency and throughput often push against each other.
Low latency means “respond to one user as fast as possible.” Achieving this often means dedicated resources, fast paths, minimal batching.
High throughput means “handle many users’ requests per second.” Achieving this often means batching, buffering, allowing some requests to wait.
Think of it like a highway:
- Low latency, low throughput: Empty highway, instant travel, but only 10 cars pass per hour.
- High latency, high throughput: Congested highway, each car takes longer, but thousands pass per hour.
You can optimize for both somewhat, but not infinitely. If you must have both (video processing: low-latency streaming and high-throughput batch processing), you often need two systems, not one.
Did you know? Netflix handles both by splitting: they have low-latency playback systems (fast decode, buffer, show video) and separate high-throughput batch systems (encoding new videos). Same company, different constraints.
When to Decide, When to Defer
Not all decisions are created equal.
Two-way door decisions are reversible. Choosing PostgreSQL instead of MySQL is annoying to reverse, but you can do it. Choosing your programming language is similar—more painful, but possible. These can be deferred or changed later.
One-way door decisions are irreversible or extremely costly. Deciding to store passwords in plain text (instead of hashed). Committing to a specific data model that’s baked into millions of user records. Starting a company in a heavily regulated industry without compliance expertise. These deserve careful, slow deliberation.
The temptation: make all decisions like they’re one-way doors. This leads to paralysis and over-engineering.
The opposite temptation: defer all decisions, hoping clarity emerges. This leads to technical debt.
Good practice: Identify which decisions are which. Make one-way door decisions carefully, with input and documentation. Make two-way door decisions quickly, knowing you can adjust.
Three Mistakes Teams Make (And How to Avoid Them)
Mistake 1: Optimizing for constraints that don’t exist. You over-engineer for global scale when you don’t need it. You build for millions of users when thousands suffice. You add complexity for compliance you don’t legally need. Avoid this: Always ask, “Is this constraint real?” If you’re not sure, it’s not real enough for architecture decisions. Build for your actual constraint, not an imagined future one.
Mistake 2: Hiding constraints instead of naming them. Teams avoid saying “we’re choosing eventual consistency because we can’t afford the latency of strong consistency.” Instead, they pretend it’s a preference. When reality hits, people blame the choice instead of understanding the constraint. Avoid this: Be explicit. Write it down. “We chose X because of constraint C.”
Mistake 3: Treating trade-offs as mistakes. A system that prioritizes consistency over availability isn’t “poorly designed”—it’s intentionally designed. A system that favors simplicity over flexibility isn’t “unambitious”—it matches its constraints. Avoid this: Stop looking for designs that have no trade-offs. They don’t exist. Look for designs where the trade-offs match your constraints.
Key Takeaways
- Constraints come from technology, business, regulation, and team skills. Identify them explicitly before designing.
- Trade-offs are inevitable. Your job is making them consciously, not stumbling into them by accident.
- CAP theorem and PACELC are practical guides, not theoretical trivia. They help you think clearly about failure modes.
- Consistency models range from strong to eventual, each with different latency and coordination costs.
- Latency and throughput often trade off; optimize for what matters to your users given your constraints.
- Document why you chose what you chose, especially for one-way door decisions.
- The best systems design doesn’t eliminate trade-offs—it makes them intentionally and aligns them with constraints.
Practice Scenarios
Scenario 1: The Startup Dilemma
Your team is building a real-time analytics dashboard for a venture capital firm. They want to see which startups are trending every 5 minutes, with 99.9% uptime. Your constraints: three engineers, $5k/month cloud budget, six-week launch. Should you use a distributed database with strong consistency, or a simple PostgreSQL instance with eventual consistency caching? Document your choice, trade-offs, and constraints.
Scenario 2: The Geographic Expansion
Your e-commerce company is expanding from the US to Europe. You currently use a single PostgreSQL database in Virginia. Customers are complaining about 200ms latency from Europe. Your options: add read replicas (eventual consistency), shard by geography (complexity), or use a global database (cost). List your constraints, rank your options with a decision matrix, and explain which trade-offs you’re accepting.
Scenario 3: The Regulatory Surprise
Three months after launching, you discover your financial data storage must comply with new regulations requiring that all data be deleted after 90 days, with an audit trail. Your current system stores everything forever in a single immutable table. Map out this constraint’s impact on your architecture, and propose a redesign that accommodates it.
Building Toward Requirements Translation
You’ve now explored how to gather requirements, understand scale and users, and identify the constraints and trade-offs that shape design. In Chapter 3, we’ll bring all of this together. We’ll learn how to translate these requirements and constraints into a concrete architectural specification—the blueprint for what you actually build.
You’ll discover how Netflix handles 200 million users across the globe, how Stripe manages fraud while maintaining trust, and how systems can handle failures gracefully. But first, you’ll learn the language of system design: how to talk about capacity, throughput, latency, and reliability in ways that turn vague business goals into specific technical targets.
Ready to build something real? Let’s go.