Graph databases: when the relationships are the data
Some data is mostly about connections. Who follows whom, which account transferred to which, what depends on what, how you are related to a stranger through mutual friends. When the relationships are the point — not an incidental foreign key but the actual thing you query — a graph database earns its place. Neo4j is the best-known; Amazon Neptune and others follow the same idea.
The problem: relationships that go deep
Relational databases handle relationships through joins, and for one or two levels that is fine. The trouble is depth. Consider "friends of friends of friends" on a social network:
-- three levels deep, relationally
SELECT DISTINCT f3.friend_id
FROM friendships f1
JOIN friendships f2 ON f2.user_id = f1.friend_id
JOIN friendships f3 ON f3.user_id = f2.friend_id
WHERE f1.user_id = 42;
Each level is another self-join, and the cost multiplies at each hop — the fan-out from module 5. Six degrees of separation is a six-way join over a table of millions, and it can become minutes or simply infeasible. "Shortest path between two people", "all accounts within four transfers of this one" — these are painful or impossible to express in SQL, and ruinous to run.
The reason is structural: a relational join searches for matching rows by value at query time (even with an index, it is a lookup per row). A graph database instead stores, on each node, direct pointers to its neighbours — so traversing an edge is following a pointer, not searching a table. This is called index-free adjacency, and it is the whole trick: traversal cost depends on how much of the graph you touch, not on how big the graph is. Friends-of-friends costs the same on a graph of a thousand or a billion, as long as the neighbourhood you walk is the same size.
The model: nodes, relationships, properties
A graph is:
- Nodes — the entities (a person, an account, a product).
- Relationships — typed, directed edges between nodes (
FOLLOWS,TRANSFERRED_TO,DEPENDS_ON). Relationships are first-class: they have a type, a direction, and their own properties. - Properties — key-value pairs on both nodes and relationships (a person's name; a transfer's amount and date).
Neo4j's query language is Cypher, and its genius is that it draws the pattern as ASCII art:
// nodes in (parentheses), relationships in [brackets], arrows for direction
CREATE (kavita:Person {name: 'Kavita'})-[:FOLLOWS]->(ravi:Person {name: 'Ravi'})
// friends of friends of friends — the query that hurts SQL
MATCH (me:Person {name: 'Kavita'})-[:FOLLOWS*3]->(fof)
RETURN DISTINCT fof.name
// shortest path between two people
MATCH p = shortestPath(
(a:Person {name: 'Kavita'})-[:FOLLOWS*]-(b:Person {name: 'Anjali'})
)
RETURN p
(a)-[:FOLLOWS]->(b) literally looks like the relationship it matches, and [:FOLLOWS*3] means
"follow this edge three times". The variable-depth traversal (*) and shortestPath are one-liners
that have no clean SQL equivalent at all. That expressiveness for connected queries is the
headline benefit — the query reads like the question.
Where graphs genuinely win
The pattern is: the relationships are the data, and you traverse them, often to variable depth.
- Social networks — friends-of-friends, mutual connections, "people you may know", degrees of separation.
- Recommendation engines — "customers who bought this also bought", "products connected to things you liked" — traversing a web of associations.
- Fraud detection — rings of accounts connected through shared devices, addresses or transfers; finding the cycle is a graph query and a nightmare in SQL.
- Knowledge graphs — entities and their relationships, the structure behind many question-answering and search features.
- Network and dependency mapping — infrastructure topology, package dependency trees, "what breaks if this service goes down".
- Identity and access — who can reach what through chains of group memberships and permissions.
If your questions routinely contain "connected to", "path between", "within N hops of", or "related through", that is the signal.
Where graphs lose
As specialised as any family here, and the wrong default for most applications:
- Simple, tabular, aggregate-heavy data. "Total sales by month" is a relational
GROUP BY; a graph adds nothing and complicates everything. - When relationships are shallow. One or two joins deep, a relational database is faster and simpler. The graph's advantage only shows at depth.
- Bulk analytics across all nodes. Graphs are optimised for traversing from a starting point, not scanning the whole dataset for aggregates.
- Operational maturity. A separate specialised system to run, back up, and staff; the ecosystem is smaller than PostgreSQL's.
And PostgreSQL can go surprisingly far before you need a dedicated graph database: a recursive
CTE (WITH RECURSIVE) handles hierarchies and moderate-depth traversals, and the pgRouting and
Apache AGE extensions add real graph capability. For a modest amount of connected data, staying in
PostgreSQL is often right — the same "one system is simpler" argument as everywhere in this course.
How to recognise the need
Choose a graph database when:
- Relationships are central, queried as much as or more than the entities themselves.
- Traversals go deep or to variable depth — three or more hops routinely, or "shortest path".
- The connection patterns matter — rings, paths, mutual links, reachability.
- The relational version is a pile of self-joins that are slow or unwritable.
And do not, when your data is fundamentally tabular, your relationships are shallow, or a recursive CTE in the database you already run would do. The test: are you querying the connections themselves, to depth? Yes — consider a graph. No — you have a relational schema with foreign keys, and that is fine.
Check your work
When a graph database earns its place. When the relationships are the data and you traverse them, often to variable depth.
Why deep relationships hurt relational databases. Each level is another self-join and the cost multiplies per hop (fan-out).
Index-free adjacency. Each node stores direct pointers to its neighbours, so traversing an edge follows a pointer instead of searching a table.
Its consequence. Traversal cost depends on the neighbourhood you touch, not the size of the whole graph.
The three parts of the model. Nodes, typed and directed relationships (first-class, with their own properties), and properties on both.
What Cypher's syntax mimics. The shape of the pattern — (a)-[:FOLLOWS]->(b) looks like the
relationship.
Two queries with no clean SQL equivalent. Variable-depth traversal ([:FOLLOWS*3]) and
shortestPath.
Five domains where graphs win. Social networks, recommendations, fraud detection, knowledge graphs, dependency/network mapping.
The signal words. "Connected to", "path between", "within N hops", "related through".
Where graphs lose. Tabular aggregate data, shallow relationships, whole-dataset analytics, and the extra operational cost.
PostgreSQL's own graph reach. Recursive CTEs, and the pgRouting / Apache AGE extensions — often enough for modest connected data.
Practice
- Write the SQL for "friends of friends of friends" as three self-joins. Reason about its cost on a million-row friendship table.
- Write the same in Cypher with
[:FOLLOWS*3]. Compare the readability. - Explain index-free adjacency in your own words, and why it makes graph size irrelevant to a local traversal.
- Model a small social graph as nodes and relationships. Add a property to a relationship (e.g.
sinceonFOLLOWS). - Describe a fraud-detection query ("accounts sharing a device within three transfers") and why it is hard in SQL.
- Write a
WITH RECURSIVEquery in PostgreSQL to traverse a hierarchy, and note where it stops being pleasant. - Take an application you know and decide whether any part of it is genuinely graph-shaped. Be honest — most are not.
- For one that is not, explain why foreign keys and a couple of joins are the better answer.
Official documentation
- Neo4j — Documentation — The property-graph model and operations.
- Neo4j — Cypher query language — Pattern matching, variable-depth traversal and
shortestPath. - Neo4j — Graph database concepts — Nodes, relationships, and index-free adjacency explained.
- PostgreSQL — WITH queries (recursive CTEs) — How far the relational database goes on its own.
- Apache AGE — A graph extension for PostgreSQL, if you want graph queries without a second system.
Next: search engines, and why LIKE is not search.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship