#09Build a graph database (Neo4j / Dgraph-style)
When the workload is 'friends of friends', a relational join melts. Build a store where edges are first-class — index-free adjacency, traversals that follow pointers instead of joining tables, and a query language (Cypher / GraphQL+) that thinks in patterns. Feel why graph storage shines for traversal-heavy work and stumbles on full-graph aggregates.

You are building the simplest thing that could possibly answer one question: "how is this connected to that?" Friends of friends, the parts that make up an assembly, the accounts a fraud ring routes money through, the page that links to the page that links to yours. The shape is always the same — entities joined by relationships — and the query is almost always "walk the relationships and tell me what you reach."

The obvious plan is a relational one: a USER_FRIEND(user, friend) join table. It works beautifully for one hop. But "friends of friends of friends" is the same table self-joined, once per hop, and the intermediate result explodes combinatorially. With a million users averaging 50 friends each, a depth-4 query materializes on the order of 6.25 million candidate rows (depth-5: ~312 million) that the engine must build, sort, and dedupe. In the classic Neo4j in Action benchmark, that depth-4 query ran in ~1.3 s on a graph store while MySQL took ~1,543 s — and could not finish depth 5 at all. The join didn't get slow; it melted.

So you change the storage, not the query. You make edges first-class: every node stores a direct pointer to its own relationship records, so following an edge is a pointer dereference — roughly O(1), and independent of how big the graph is — instead of a B-tree index seek that pays O(log N) on every hop and gets slower as the data grows. That one trick is index-free adjacency, and it is the entire reason this category of database exists. The tagline for the whole course: follow pointers, don't join tables.

The honest other half — which you will build toward and feel directly — is that the same design has no locality for whole-graph work. A k-hop traversal lights up a few nodes; PageRank lights up every node, every pass. Global aggregates have nothing for index-free adjacency to exploit, and you fall back to the Pregel / "think like a vertex" world of supersteps and barriers. Resist the urge to "describe Neo4j." Build the store yourself — the records, the pointer chains, the pattern compiler, the locks, the partition cut — and you will know exactly where a graph database shines and exactly where it stumbles.

Reading: Robinson, Webber, Eifrem — Graph Databases (O'Reilly, 2nd ed) · Neo4j docs — Native graph storage and processing · Dgraph docs — Distributed graph architecture · TigerGraph technical whitepaper — Native parallel graph · Malewicz et al. — Pregel: A System for Large-Scale Graph Processing · JanusGraph docs — Backend-agnostic distributed graph
property graph model: nodes, edges, labels, properties
index-free adjacency — each node stores its edge list
edge stores: doubly-linked lists vs adjacency arrays vs compressed CSR
traversal-as-pointer-chase (no join planner needed for k-hop)
pattern matching: Cypher MATCH, SPARQL triples, GraphQL+
distributed graphs: vertex-cut vs edge-cut partitioning
the supernode problem (one celebrity, 10M edges)
global indexes: full-text, range, vector — bolted on, not native
ACID transactions over a graph (Neo4j) vs eventual (Dgraph)
graph algorithms layer: BFS, shortest path, PageRank