Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

Graph Databases

6 min read

In a Nutshell

A graph database stores data as nodes (entities) and edges (relationships between entities), both of which can have properties. This makes them extraordinarily efficient at answering questions that involve traversing relationships: "Who are my friends' friends?", "What products were bought by people who also bought X?", "Which accounts in this transaction chain are connected to a flagged entity?" In a relational database, these queries require multiple self-joins that become exponentially slower as the depth increases. In a graph database, traversal is a constant-time operation per hop — regardless of the total dataset size.

2D minimalistic graph showing nodes as circles labeled with entity types (Person, Product, Company) connected by labeled edges (KNOWS, BOUGHT, WORKS_AT), with properties listed inside each node

How It Actually Works

The Property Graph Model

(Alice)──[FOLLOWS]──▶(Bob)
   │                    │
   │                    │
[LIKES]              [LIKES]
   │                    │
   ▼                    ▼
(Post:123)          (Post:456)
   │
[TAGGED]
   │
   ▼
(Topic:Music)
Element Description Example
Node An entity (with a label and properties) (:Person {name: "Alice", age: 30})
Edge A relationship (with a type, direction, and properties) [:FOLLOWS {since: "2023-01-01"}]
Property Key-value pairs on nodes or edges name: "Alice", weight: 0.95
Label Category for a node :Person, :Product, :Company

Neo4j and Cypher

Neo4j is the most popular graph database. Its query language, Cypher, reads like ASCII art:

-- Find Alice's friends who also like the same posts
MATCH (alice:Person {name: "Alice"})-[:FOLLOWS]->(friend)-[:LIKES]->(post)
WHERE (alice)-[:LIKES]->(post)
RETURN friend.name, post.title

-- Find the shortest path between two users
MATCH path = shortestPath(
  (alice:Person {name: "Alice"})-[:FOLLOWS*..6]-(bob:Person {name: "Bob"})
)
RETURN path

-- Recommend products: "people who bought X also bought..."
MATCH (u:User)-[:BOUGHT]->(p:Product {name: "Headphones"})
      <-[:BOUGHT]-(other:User)-[:BOUGHT]->(rec:Product)
WHERE NOT (u)-[:BOUGHT]->(rec)
RETURN rec.name, count(other) AS buyers
ORDER BY buyers DESC LIMIT 5

When to Choose Graph Databases

Use when:

  • The value is in the relationships, not just the entities
  • Queries traverse multiple hops (friends of friends, supply chains, fraud rings)
  • Relationship patterns are complex and varied (social graphs, knowledge graphs)
  • You need real-time traversal, not batch analytics

Don't use when:

  • Data is tabular with simple relationships (use SQL)
  • You need full-text search or aggregation (use Elasticsearch or SQL)
  • The primary access pattern is key-based lookup (use key-value)
  • You need massive write throughput of flat records (use wide-column)

Graph vs SQL for Relationship Queries

Query SQL Graph
"Who does Alice follow?" 1 JOIN 1-hop traversal
"Who are Alice's friends' friends?" 2 self-JOINs 2-hop traversal
"Is there a connection within 6 hops?" 6 self-JOINs (exponential) Path query (linear time per hop)
"Shortest path between A and B" Recursive CTE (painful) Built-in shortestPath()

The key insight: SQL join cost grows exponentially with depth. Graph traversal cost grows linearly.

2D minimalistic split diagram: left side shows a SQL query with multiple JOIN boxes getting exponentially wider at each level, right side shows a graph traversal as a simple path through connected nodes, both answering the same 'friends of friends of friends' query

Seeing It in Action

Scenario: Fraud detection in a financial network

-- Find circular money flows (potential money laundering)
MATCH path = (a:Account)-[:TRANSFERRED_TO*3..6]->(a)
WHERE ALL(t IN relationships(path) WHERE t.amount > 10000)
RETURN path

-- Find all accounts within 3 hops of a flagged account
MATCH (flagged:Account {status: "FLAGGED"})-[:TRANSFERRED_TO*1..3]-(connected)
RETURN connected.id, connected.owner,
       length(shortestPath((flagged)-[:TRANSFERRED_TO*]-(connected))) AS distance
ORDER BY distance

Why a graph database is right here:

  • Circular path detection — Finding cycles in a financial network is a native graph operation
  • Variable-depth traversal — "Within 3 hops" is trivial; in SQL this requires 3 self-joins with UNIONs
  • Real-time — Fraud detection needs immediate answers, not batch processing
  • The data IS a graph — accounts and transfers are naturally nodes and edges

Interview Questions

  1. Q: When would you choose a graph database over SQL for social features? Hint: When queries involve multi-hop traversals: recommendations, mutual friends, "6 degrees of separation," influence scoring. For simple "get my followers" (1-hop), SQL with an index is fine. Graph databases shine at 2+ hops where SQL self-joins become exponentially expensive.

  2. Q: Design a graph data model for a recommendation engine ("people who bought X also bought Y"). Hint: Nodes: :User, :Product, :Category. Edges: [:BOUGHT], [:VIEWED], [:BELONGS_TO]. Query: Start from the product, traverse to users who bought it, traverse to other products they bought, aggregate by frequency. Edge properties like timestamp and rating can weight recommendations.

  3. Q: What are the scaling limitations of graph databases? Hint: Graph traversals are inherently hard to shard — a traversal may need to cross shard boundaries (network hops). Neo4j scales reads via replicas but the full graph must fit on one machine's storage (or use Neo4j Fabric for federated queries). For massive graphs, distributed options like Amazon Neptune or JanusGraph exist but add latency per hop.

  4. Q: Can you use PostgreSQL for graph queries instead of a dedicated graph database? Hint: Yes, using recursive CTEs (WITH RECURSIVE). It works for simple graph queries (2–3 hops, small graphs). It breaks down at: deep traversals (6+ hops), large graphs (millions of edges), complex path algorithms (shortest path, cycle detection). If graph queries are your primary workload, a dedicated graph DB is significantly faster.

  5. Q: How would you combine a graph database with a relational database in the same system? Hint: Polyglot persistence: SQL for transactional data (orders, payments), graph DB for relationship-heavy data (social graph, recommendations). Sync between them via CDC (Change Data Capture) or dual writes. Example: LinkedIn uses a graph for the social network but relational databases for job postings and payments.

References

Dive Deeper