Session 1 · Week 1 · Tue 3 Nov 2026 · 75 min

The 6 core trade-offs.

There are no right answers in architecture. There are only positions on a handful of axes, chosen on purpose. Tonight we name all six, so you stop guessing and start deciding out loud.

Ehsan Gazar
From Senior to Staff · session 1 of 8
Where we're going · 90 minutes

The run sheet.

0–10The reframewhy "it depends" is the answer
10–55The six axes~7 min each, with a concrete call
55–70One decision, many axesa single cache moves three
70–85Live: locate your decisionyou bring a real one
85–90Recap + Project 1the worksheet, due Jun 12
By the end of tonight

You'll be able to…

1Name which axis a design decision actually lives on, instead of arguing technologies.
2State your position on that axis and the user-visible consequence of it.
3Spot when one tactic moves several axes at once, and account for all of them.
4Name the trigger that would make you revisit the call, so it reads as deliberate, not lucky.
The reframe

"It depends" is the answer.
The skill is saying on what.

Junior engineers want the right database. Staff engineers know the database is downstream of a position they took on an axis. Architecture is choosing where to sit on each trade-off, deliberately, and being able to say why.

💡 In plain English

There's rarely a single "best" choice. There's only "best for what you care about most right now." Picking fast usually means giving up cheap, or simple, or something else, and that's completely fine, as long as you can say what you gave up and why.

"What's the best X?"
looking for a default to copy
junior framing
→ reframe →
"What am I trading, and for what?"
a position you can defend
staff framing
The map

Six axes. Almost every decision is on one of them.

1
Consistency ↔ Availability
what happens during a partition
2
Latency ↔ Throughput
fast per request vs many requests
3
Read ↔ Write
optimize one path, pay the other
4
Space ↔ Time
store more to compute less
5
Coupling ↔ Flexibility
simple now vs changeable later
6
Cost ↔ Everything
every 9 and every ms has a price
Axis 1 · Consistency ↔ Availability

Decide what happens when the network splits.

CAP is the soundbite; PACELC is the useful form. Read it as one question with two branches, and notice you're paying somewhere on both.

flowchart TB
  P{"Network partition
happening?"} P -- "Yes · the P" --> PICK{"Pick one,
you can't keep both"} PICK -- Availability --> AP["Cart · feed · presence
serve stale, reconcile later"] PICK -- Consistency --> CP["Ledger · inventory · auth
refuse rather than be wrong"] P -- "No · the E (Else)" --> ELSE{"Even healthy,
still trading"} ELSE -- Latency --> L["answer fast,
guarantee weaker"] ELSE -- Consistency --> C["guarantee strong,
wait a little longer"] classDef ap fill:#DCEBFE,stroke:#1F2937,color:#0E1726; classDef cp fill:#FEF3C7,stroke:#1F2937,color:#0E1726; classDef ask fill:#ffffff,stroke:#1F2937,color:#0E1726; class AP,L ap; class CP,C cp; class P,PICK,ELSE ask;

The staff tell: name the user-visible effect on a leaf, "they see a stale count for ~2s," not "eventually consistent."

Axis 2 · Latency ↔ Throughput

Batching buys throughput with someone's latency.

Every batch, queue, and buffer raises requests-per-second and adds delay to the request that waits for the batch. They are not the same number and they do not optimize together.

Latencytime for one request · graded at p99, not the mean
Throughputrequests served per second across the whole system
The leverbatch / buffer / async → throughput up, tail latency up
The tellstate the budget: "p99 < 50 ms" and which one you're protecting before you reach for a queue
Axis 3 · Read ↔ Write

You can make reads cheap or writes cheap. Pick the hot one.

The classic case is the feed. Same post, two ways to spend the work, and the bill just moves between the write path and the read path.

flowchart LR
  POST(["New post"])
  POST --> FW["Fan-out on WRITE
copy into every follower timeline now"] POST --> FR["Store once
normalized, do nothing extra"] FW --> FWR(["Read = 1 cheap lookup
but a celebrity's write fans out to millions"]) FR --> FRR(["Read = join across everyone you follow
cheap writes, slow reads"]) FWR --> HY{"Hybrid:
push for most,
pull for celebrities"} FRR --> HY classDef w fill:#D6F5E3,stroke:#1F2937,color:#0E1726; classDef r fill:#FEE4E2,stroke:#1F2937,color:#0E1726; classDef h fill:#FEF3C7,stroke:#1F2937,color:#0E1726; class FW,FWR w; class FR,FRR r; class HY h;

The tell: find the hot path, optimize it, and say out loud what you're paying on the cold one (here, write amplification and drift you must reconcile).

Axis 4 · Space ↔ Time

Spend storage to buy latency, and say what staleness you bought.

Caches, indexes, materialized views, denormalized copies, bloom filters: all the same move. You hold extra bytes so you can skip computation. The hidden cost is rarely the bytes, it's the staleness and invalidation.

Cache
RAM + staleness → fewer recomputes
Index
disk + slower writes → fast lookups
Precompute
storage + a pipeline → instant reads
The tell
"I'll accept up to N seconds stale here" names the real cost
Axis 5 · Coupling ↔ Flexibility

Couple by default. Split on a real axis of change.

A monolith is cheap to build, deploy, and reason about, and it couples everything's release and scaling together. Services buy independent deploy and scale, and charge you network calls, partial failure, and distributed data. Splitting for the org chart, not a real axis, is how you get a distributed monolith.

Keep together
shared lifecycle, shared data, one team, simple ops
default
Split when
different scaling need, different rate of change, or a hard failure boundary
earn it
The trap
splitting on the org chart → a distributed monolith, all the cost, none of the gain
avoid
Axis 6 · Cost ↔ Everything

Every extra 9 and every saved millisecond has a bill.

Performance, reliability, and time-to-market are all bought with money or time. Active-active multi-region roughly doubles infra for one more 9. Build-vs-buy is this axis. The staff move is tying the spend to a business number, not "best practice."

99.9% → 99.99%~8.8h → ~52m downtime/yr · often 2× infra for the redundancy
Build vs buybuy = $/mo + lock-in · build = headcount + maintenance forever
The tell"this 9 is worth it because an hour down costs us $X" beats "we should be highly available"
One decision, many axes

"Reads are slow, let's add a cache."

One sentence, three axes moved at once. Staff engineers don't say "add a cache," they account for all three out loud.

💡 In plain English

A "cache" is just a small fast copy of data you keep close so you don't have to fetch the real thing every time, like jotting a phone number on a sticky note instead of looking it up in the contacts app each call. The catch: when the real number changes, your sticky note is now wrong.

Read ↔ Write

You optimized the read path and added write-side work to keep the cache warm and correct.

Space ↔ Time

You spent RAM to skip computation, and accepted a staleness window you should name.

Consistency ↔ Availability

A cache that can serve stale on a miss-storm is an availability/consistency choice too.

The point

Cheap tactics are rarely free, they relocate the cost to another axis. Track where it went.

The repeatable move

Axis → Position → Consequence → Trigger.

name
The axis
"this is a read-vs-write call"
take
A position
"I optimize reads"
own
The consequence
"writes do more, ~2s stale"
defer
The trigger
"revisit if writes get hot"

Four sentences. Run them on any decision and you sound like you chose it, because you did.

Your turn · ~12 min

Locate a real decision on the axes.

Bring a choice from your current system: a cache, a queue, a split, a database pick. We'll do one together, then you run the four sentences on yours and drop the axis + position in the chat.

1
Which axis?
name the dominant one (often two)
2
Which position?
and the user-visible consequence
3
What's the trigger?
what would make you move
The traps

Four ways engineers dodge the trade-off.

1
Cargo-culting a default
"we use Postgres / Kafka here" with no axis named.
2
Wanting both ends
"fast and consistent and cheap" — you picked nothing.
3
Hiding the cost
"just add a cache" without naming the staleness.
4
No revisit trigger
a permanent decision for a temporary load.
Recap · then Project 1

Six axes, one sentence each.

Consistency↔Availability · Latency↔Throughput · Read↔Write · Space↔Time · Coupling↔Flexibility · Cost↔Everything. For every call: name the axis, take a position, own the consequence, set the trigger.

Project 1 · Capacity Estimation Worksheet
Friday's session feeds it. Due Jun 12.
Session 2 = the numbers behind these axes
Take-home
Run the four sentences on one decision in your codebase this week.
bring it Friday