Learning path
Hierarchies in Backend Design
Mission: MISSION.md ·
Sources: RESOURCES.md · Calibrated 2026-08-18 · 5 lessons available
mastered / completed
taught, not yet completed
outer fringe — ready, no lesson yet
locked
Amber turns green as you mark lessons complete — the graph is live, not a
snapshot. Click a node to open its lesson.
flowchart TD
T1["Type vs value hierarchy
fixed-depth vs recursive"]
T2["Adjacency list"]
T3["Materialised path / ltree"]
T4["Read-vs-write asymmetry"]
T5["Recursive CTE mechanics"]
T6["Nested set: why it lost"]
T7["Closure table + transitive closure"]
T8["The 4x5 operation matrix
the decision frame"]
T9["Single source of truth
vs derived data"]
T10["N+1 over a tree"]
A1["Aggregate = consistency boundary
invariant, not relatedness"]
A2["Small aggregates & OCC contention"]
A3["Reference by identity"]
A4["Client-generated identity
4 bug classes"]
A5["Deferrable constraints"]
A6["Cascade down, never up"]
A7["Bounded subtree read"]
C1["Standard vs custom methods
changes rows the caller didn't name"]
C2["Nest or flatten the URL
finite route template test"]
C3["UUIDv7 vs v4
index locality vs leakage"]
C4["Idempotency key vs client IDs"]
C5["parentId is not a PATCH field"]
D1["Concurrent-move write skew
lock scope = invariant scope"]
D2["Closure-table move in 2 statements"]
D3["Sibling order: fractional indexing"]
D4["Move = removal + insertion
two authorisation checks"]
D5["Desired state over deltas"]
E1["Dual-write problem"]
E2["Transactional outbox + relay"]
E3["At-least-once + inbox dedup"]
E4["Per-aggregate ordering
+ orphan tolerance"]
E5["Compensating action
& reconciliation"]
N4["Permission inheritance
over a tree"]
N5["DAGs / multi-parent
when a tree is a lie"]
N6["Trash, restore, and
undo of a subtree delete"]
T1 --> T8
T2 --> T5
T2 --> T8
T3 --> T8
T4 --> T8
T2 --> T10
T6 --> T8
T7 --> T8
T8 --> T9
T5 --> T9
T1 --> A1
A1 --> A2
A1 --> A3
A3 --> A4
A4 --> A5
A1 --> A6
T8 --> A7
T10 --> A7
A1 --> C1
T1 --> C2
A4 --> C3
A4 --> C4
C1 --> C5
C5 --> D1
T7 --> D2
D1 --> D4
T4 --> D3
C4 --> D5
A6 --> E1
C1 --> E1
E1 --> E2
T9 --> E2
E2 --> E3
E3 --> E4
A4 --> E4
A2 --> E5
E4 --> E5
T7 --> N4
A1 --> N4
T8 --> N5
C2 --> N5
A6 --> N6
E5 --> N6
classDef mastered fill:#d8ecd8,stroke:#5a7a5a,color:#1a1a18
classDef inner fill:#fde9b8,stroke:#a8862a,color:#1a1a18
classDef outer fill:#e2eafc,stroke:#4a6ea8,color:#1a1a18
classDef locked fill:#f2f0ea,stroke:#a09a8a,stroke-dasharray:4 3,color:#5d574a
class T2,T3,T4 mastered
class T1,T5,T6,T7,T8,T9,T10,A1,A2,A3,A4,A5,A6,A7,C1,C2,C3,C4,C5,D1,D2,D3,D4,D5,E1,E2,E3,E4,E5 inner
class N4,N5,N6 outer
click T1 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T5 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T6 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T7 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T8 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T9 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click T10 "./lessons/a1f3-four-ways-to-store-a-tree.html"
click A1 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A2 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A3 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A4 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A5 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A6 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click A7 "./lessons/b7c2-aggregate-boundaries-in-hierarchies.html"
click C1 "./lessons/c4e9-rest-commands-and-identity.html"
click C2 "./lessons/c4e9-rest-commands-and-identity.html"
click C3 "./lessons/c4e9-rest-commands-and-identity.html"
click C4 "./lessons/c4e9-rest-commands-and-identity.html"
click C5 "./lessons/c4e9-rest-commands-and-identity.html"
click D1 "./lessons/d8b1-the-move-endpoint.html"
click D2 "./lessons/d8b1-the-move-endpoint.html"
click D3 "./lessons/d8b1-the-move-endpoint.html"
click D4 "./lessons/d8b1-the-move-endpoint.html"
click D5 "./lessons/d8b1-the-move-endpoint.html"
click E1 "./lessons/e5f7-outbox-and-eventual-consistency.html"
click E2 "./lessons/e5f7-outbox-and-eventual-consistency.html"
click E3 "./lessons/e5f7-outbox-and-eventual-consistency.html"
click E4 "./lessons/e5f7-outbox-and-eventual-consistency.html"
click E5 "./lessons/e5f7-outbox-and-eventual-consistency.html"
Lessons
| # | Lesson | Covers |
| 1 |
Four ways to store a tree |
4×5 operation matrix; recursive CTE; nested set & closure table; type vs value hierarchy as the outranking
question; single source of truth vs derived data; tree N+1 |
| 2 |
Where the transaction ends |
Aggregate as consistency boundary; contention from oversized aggregates; client-generated identity and its four
bug classes; deferrable constraints; cascade policy; bounded subtree reads |
| 3 |
REST, commands, and who invents identity |
Standard vs custom methods; nest-or-flatten the URL; UUIDv7 vs v4 with the counter-argument; idempotency key vs
client IDs; why parentId must not be a PATCH field |
| 4 |
The move endpoint |
Concurrent-move write skew and lock scope; the closure-table move; fractional indexing for sibling order; two
authorisation checks; desired state over deltas |
| 5 |
Eventual consistency between aggregates |
Dual-write problem; transactional outbox and relay; at-least-once with inbox dedup; per-aggregate ordering and
orphan tolerance; compensating actions and reconciliation |
Calibration record — 2026-08-18
| Probe | Result | Addressed |
Subtree query on parent_id; name the model |
Gap. Names the model but delegates the query to an ORM — no recursive-CTE mental model,
so no way to reason about read cost. Implies exposure to the tree N+1 trap. | L1 §2, L1 Ex 2 |
| Move cost: materialised path vs adjacency list |
Solid. 50k vs 1, with the right read/write inference unprompted. | — |
| Aggregate boundary for campaign/ad set/ad |
Right answer, wrong reason. Chose three, justified by a validation gate — a state
machine, not an invariant. | L2 §1 |
| Dirty state / relaxed referential integrity |
Gap. No articulated bug classes. | L2 §3, L5 §4 |
ON DELETE CASCADE as product semantics |
Gap (self-reported). | L2 §4 |
Feedback incorporated
- L1: mermaid copy buttons added everywhere (fenced output, for Obsidian); "cache, don't fork"
replaced with single source of truth / derived data plus the rebuild test — it was an informal phrase
used as if canonical; this STATUS graph made live rather than static.
- L2: "why UUIDv7 rather than v4?" — a bare assertion in L2, now a full section in L3 §3 with the
index-locality mechanism, Brooker's four costs, and the client-clock wrinkle that applies specifically to
client-generated IDs.
Teaching preferences
- Code examples in TypeScript; SQL in PostgreSQL dialect.
- Wants the problem space mapped before any specific system — lessons stay scenario-based, not codebase-based.
- Answers calibration honestly including "don't know" — probes can be pitched harder than usual.
- Thinks in trade-off matrices — lean on tables and decision flows over prose.
- Keeps notes in Obsidian. Every diagram needs a copy button emitting a fenced
mermaid
block. Favour portable artefacts over prose-only explanations.
- Expects state to be live, not narrated. If a file claims to track progress, it must reflect it.
- Challenges asserted defaults. Any "use X" recommendation needs its mechanism and its
counter-argument stated, not just the conclusion. This is the highest-value preference on the list — treat unexplained
best-practice advice as a defect.
- Session length still unstated; defaulting to short single-idea lessons. Open question.
Next iteration — the outer fringe
- Permission inheritance over a tree (N4) — the closure table earns its keep here; this is where
"who can see this folder" meets 50k descendants. Was out of scope in the mission; now the natural next step.
- DAGs and multi-parent (N5) — what to do when the tree is a lie: tags, shared folders, symlinks,
org charts with dotted-line reports.
- Trash, restore, undo (N6) — the gap flagged in RESOURCES; L2 §4 promised soft delete but never
covered restore semantics or orphan reparenting.
Say which one, or ask for a calibration round first if you'd rather I re-probe before
writing.