When does a relational model break down enough that you'd reach for a graph database?
Relational joins get slower the deeper a relationship goes; graph databases store the relationship itself as a first-class, indexed pointer, so traversing five hops costs about the same as traversing one.
LEARN BY DOINGPut your intuition to work3 steps
When does a relational model break down enough that you'd reach for a graph database?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Relationships as data, not as joins
Every relational database can model relationships — that's what foreign keys and join tables are for. What it can't do cheaply is traverse a relationship whose depth you don't know in advance. "Find my friends" is one join. "Find people connected to me through mutual friends" is two. "Find everyone reachable from me through this network, however many hops it takes" is a recursive query that a relational engine has to grind through join-by-join, level by level, and whose cost grows with how much of the table it has to touch at each level.
A graph database inverts the cost model. Nodes and relationships are stored so that each node physically knows its own adjacent relationships — a technique usually called index-free adjacency. Traversing from one node to its neighbors is a pointer hop, not an index lookup into a potentially huge table, so the cost of a traversal is proportional to the size of the neighborhood you actually visit, not the size of the whole dataset. A five-hop traversal over a graph with 50 million nodes can cost about the same as a five-hop traversal over a graph with 500 — because the total size of the graph is never actually consulted for the parts you don't touch.
#When the relational model actually breaks down
- Depth is variable or unknown ahead of time. "How is Person A connected to Person B?" (any number of hops), "which permissions does this user inherit transitively through group membership?", "what breaks if I retire this microservice, considering its callers' callers?" — these are path-finding questions, and path-finding is what a graph engine is built to do well.
- The relationships are the interesting data, not the entities. Fraud rings are a textbook case: individually, ten accounts look unremarkable. What makes them a ring is that they share a device, a payment method, and an address in an interlocking pattern — the graph of connections is the signal, not any single row.
- New relationship types show up often. Adding "Person MENTORS Person" to a graph is one new relationship type; adding it to a relational schema is a new join table, new foreign keys, and a migration, for every new kind of connection your product grows.
- Recommendation and similarity queries walk shared connections. "Customers who bought what you bought also bought…" is, underneath, a multi-hop traversal (you → purchase → product → other buyers → their other purchases) that a graph query expresses directly and a SQL query expresses through several joins that get worse as you add hops.
#A concrete comparison
Here's "suggest people you might know" — friends of friends who aren't already your friends — first the way you'd have to write it against a relational schema with a recursive CTE:
WITH RECURSIVE network(person_id, depth) AS (SELECT friend_id, 1FROM friendshipsWHERE person_id = 4471UNION ALLSELECT f.friend_id, n.depth + 1FROM friendships fJOIN network n ON f.person_id = n.person_idWHERE n.depth < 2)SELECT DISTINCT person_idFROM networkWHERE depth = 2AND person_id NOT IN (SELECT friend_id FROM friendships WHERE person_id = 4471);
That's already awkward at two hops, and every additional hop means another recursive step plus more careful de-duplication. Here's the same question in Cypher, against a native graph database:
MATCH (me:Person {id: 4471})-[:FRIENDS_WITH]->(:Person)-[:FRIENDS_WITH]->(suggestion:Person)WHERE suggestion <> meAND NOT (me)-[:FRIENDS_WITH]->(suggestion)RETURN suggestion.name AS suggestion, count(*) AS mutualFriendsORDER BY mutualFriends DESCLIMIT 10;
Widening this from "friends of friends" to "anyone reachable within 4 hops" is a one-character change (*1..4 on the relationship) in Cypher; in SQL it's rewriting the recursion depth and re-checking that the de-duplication logic still holds.
MATCH (me:Person {id: 4471})-[:FRIENDS_WITH*1..4]-(reachable:Person)RETURN DISTINCT reachable.name;
In the diagram above, Cara and Eli are two and three hops from "Me" respectively — a graph engine finds both by walking physical relationship pointers outward; a relational engine finds them by re-joining the friendships table once per hop.
#Where a graph database is the wrong tool
- Bulk aggregation over large flat sets — "total revenue by region last quarter" — is still usually faster and simpler in a relational or columnar store; graph engines are optimized for traversal, not for scanning and summing millions of rows.
- Whole-graph analytics (PageRank-style centrality, community detection across the entire graph) often wants a dedicated batch graph-processing engine rather than an OLTP graph database tuned for fast individual traversals.
- Shallow, well-known relationships — a user has many orders, an order has many line items — don't need a graph database at all. A foreign key and an indexed join already answer that in effectively constant time; reaching for Neo4j or similar here adds an unfamiliar query language and a second database to operate, for no traversal benefit you're actually using.