Use Cases and Algorithms in Practice

This section documents the current Neo4j 2026.x calendar-versioned line — Neo4j moved from semantic to calendar versioning (YYYY.MM) in 2025, and the same line applies to the Graph Data Science library — as published at the Neo4j documentation, which is the reference these pages are written and verified against. No specific monthly patch is pinned. Some areas (Aura’s internal infrastructure, the Raft consensus implementation details, and the GDS Pregel API’s low-level internals) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Neo4j iterates quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

The preceding pages cover pathfinding, centrality, community detection and APOC one algorithm at a time. This page turns that catalog around and starts from five recurring business problems, showing which algorithms combine to solve each one and why that combination — rather than any single algorithm in isolation — is what actually answers the question.

Recommendation engines

A recommendation is, structurally, "find people similar to this one and suggest what they have that this one does not." Node Similarity computes a pairwise similarity score (Jaccard by default) between nodes that share neighbors — here, people who watched overlapping sets of movies — and Filtered Node Similarity narrows that computation to a chosen source set instead of scoring every pair in the graph, which is the practical form to reach for once the graph is large enough that an all-pairs comparison would be wasteful.

CREATE (ada:Person {name: 'Ada'})
CREATE (bo:Person {name: 'Bo'})
CREATE (cy:Person {name: 'Cy'})
CREATE (m1:Movie {title: 'Nine Lives'})
CREATE (m2:Movie {title: 'Red Horizon'})
CREATE (m3:Movie {title: 'Glass Orchard'})
CREATE (ada)-[:WATCHED]->(m1)
CREATE (ada)-[:WATCHED]->(m2)
CREATE (bo)-[:WATCHED]->(m1)
CREATE (bo)-[:WATCHED]->(m2)
CREATE (bo)-[:WATCHED]->(m3)
CREATE (cy)-[:WATCHED]->(m3)

CALL gds.graph.project(
  'watch-history',
  ['Person', 'Movie'],
  {WATCHED: {orientation: 'UNDIRECTED'}}
)
// Movies watched by the people most similar to Ada that Ada has not seen yet
MATCH (ada:Person {name: 'Ada'})
CALL gds.nodeSimilarity.filtered.stream('watch-history', {
  sourceNodeFilter: [ada]
})
YIELD node2, similarity
WITH gds.util.asNode(node2) AS similarPerson, similarity
ORDER BY similarity DESC
LIMIT 10
MATCH (similarPerson)-[:WATCHED]->(recommendation:Movie)
WHERE NOT (ada)-[:WATCHED]->(recommendation)
RETURN recommendation.title AS movie, sum(similarity) AS score
ORDER BY score DESC
LIMIT 5

K-Nearest Neighbors (KNN) solves the same problem approximately but scales further, since it avoids comparing every pair outright. See Filtered Node Similarity and K-Nearest Neighbors. A dedicated treatment of recommendation-engine patterns built on similarity algorithms exists — see the bibliography.

Fraud and AML ring detection

A single account rarely reveals fraud on its own; the signal is structural — a tight loop of accounts moving money between each other, plus one or two accounts that sit between that loop and the rest of the graph. Three algorithms, layered in order, surface that structure: WCC first cuts the whole transaction graph down to the connected pieces worth examining at all, Louvain then finds dense sub-communities inside each piece (a plausible laundering ring), and Betweenness Centrality ranks the accounts that bridge separate communities — the accounts most likely to be moving funds between rings rather than transacting normally within one. The first two are covered in Community Detection Algorithms and the third in Pathfinding and Centrality Algorithms.

Two suspicious transfer rings, each a tight cycle of accounts, connected through a single bridge account with visibly higher betweenness centrality than the ring members
CREATE (a1:Account {id: 'A1'})
CREATE (a2:Account {id: 'A2'})
CREATE (a3:Account {id: 'A3'})
CREATE (a4:Account {id: 'A4'})
CREATE (a5:Account {id: 'A5'})
CREATE (a6:Account {id: 'A6'})
CREATE (a7:Account {id: 'A7'})
CREATE (a1)-[:TRANSFER {amount: 5000}]->(a2)
CREATE (a2)-[:TRANSFER {amount: 4800}]->(a3)
CREATE (a3)-[:TRANSFER {amount: 4600}]->(a1)
CREATE (a3)-[:TRANSFER {amount: 4000}]->(a4)
CREATE (a4)-[:TRANSFER {amount: 3900}]->(a5)
CREATE (a5)-[:TRANSFER {amount: 3800}]->(a6)
CREATE (a6)-[:TRANSFER {amount: 3700}]->(a4)
CREATE (a4)-[:TRANSFER {amount: 1500}]->(a7)

CALL gds.graph.project(
  'transfers',
  'Account',
  {TRANSFER: {orientation: 'UNDIRECTED'}}
)
CALL gds.wcc.stream('transfers')
YIELD nodeId, componentId
RETURN gds.util.asNode(nodeId).id AS account, componentId
ORDER BY componentId;

CALL gds.louvain.stream('transfers')
YIELD nodeId, communityId
RETURN gds.util.asNode(nodeId).id AS account, communityId
ORDER BY communityId;

CALL gds.betweenness.stream('transfers')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).id AS account, score AS bridgeScore
ORDER BY bridgeScore DESC
LIMIT 5;

Against the seven accounts above, WCC returns one component (everything is reachable), Louvain splits it into the two rings, and Betweenness Centrality ranks A4 highest — it sits on the only path between Ring 1, Ring 2, and the outside contact A7, exactly the account an investigator would want flagged first. See Weakly Connected Components, Louvain and Betweenness Centrality. A dedicated treatment of graph-based fraud and anti-money-laundering detection exists — see the bibliography.

Routing and logistics optimization

Vehicle routing and delivery scheduling reduce, at the core, to weighted shortest-path search over a location graph — exactly the problem Pathfinding and Centrality Algorithms covers in depth with Dijkstra, A* and Yen’s algorithm. The only addition a logistics scenario brings is framing: the source node is a depot, the target is a delivery address, and the weight is distance, drive time, or a cost blend of both.

// Route a delivery from the depot to a customer over the projected road
// network (see Pathfinding and Centrality Algorithms for how 'roads' is built)
MATCH (depot:Location {name: 'Turin'}), (customer:Location {name: 'Florence'})
CALL gds.shortestPath.dijkstra.stream('roads', {
  sourceNode: depot,
  targetNode: customer,
  relationshipWeightProperty: 'distance'
})
YIELD totalCost, nodeIds
RETURN totalCost AS totalDistanceKm,
  [nodeId IN nodeIds | gds.util.asNode(nodeId).name] AS route

When the location graph carries real latitude/longitude properties, A* reaches the same optimal route while visiting fewer nodes on a large, geographically spread network, and Yen’s algorithm returns several ranked alternative routes instead of only the single best one — useful for offering a backup route or avoiding a closed road without recomputing from scratch. See Dijkstra Source-Target and A* Shortest Path.

Identity and access graphs (entity resolution)

Know-your-customer (KYC) and identity-and-access-management pipelines routinely ingest the same real-world person or account more than once, through different onboarding channels, spellings, or upstream systems. Deduplicating that graph is a two-step process: a Cypher query flags candidate duplicates by some blocking key (a normalized name plus date of birth, here), and apoc.refactor.mergeNodes — covered in APOC and the Extension Ecosystem — then folds each confirmed duplicate pair into a single node, combining their properties and re-pointing every relationship instead of leaving orphaned duplicate identities behind.

// Candidate duplicates: same normalized name and date of birth, ingested from
// two different onboarding channels
MATCH (i1:Identity), (i2:Identity)
WHERE id(i1) < id(i2)
  AND toLower(i1.fullName) = toLower(i2.fullName)
  AND i1.dob = i2.dob
WITH i1, i2
CALL apoc.refactor.mergeNodes([i1, i2], {
  properties: 'combine',
  mergeRels: true
})
YIELD node
RETURN node

A blocking key this loose still needs a human or a scoring model in the loop before merging in production — treat the query above as the candidate-generation step, not the final decision. See apoc.refactor.mergeNodes. A dedicated treatment of entity resolution over graphs exists — see the bibliography.

Root-cause and dependency analysis

Service-dependency graphs answer three closely related but distinct questions, each needing a different technique over the same :Service-[:DEPENDS_ON]-:Service graph.

CREATE (gateway:Service {name: 'gateway'})
CREATE (auth:Service {name: 'auth'})
CREATE (orders:Service {name: 'orders'})
CREATE (inventory:Service {name: 'inventory'})
CREATE (billing:Service {name: 'billing'})
CREATE (notifications:Service {name: 'notifications'})
CREATE (gateway)-[:DEPENDS_ON]->(auth)
CREATE (gateway)-[:DEPENDS_ON]->(orders)
CREATE (orders)-[:DEPENDS_ON]->(auth)
CREATE (orders)-[:DEPENDS_ON]->(inventory)
CREATE (orders)-[:DEPENDS_ON]->(billing)
CREATE (billing)-[:DEPENDS_ON]->(auth)
CREATE (notifications)-[:DEPENDS_ON]->(auth)

CALL gds.graph.project(
  'services',
  'Service',
  {DEPENDS_ON: {orientation: 'NATURAL'}}
)

Blast radius. Given a service that just started failing, which other services will eventually break? That is every service that transitively depends on it — a variable-length traversal followed upstream:

// Every service an `auth` outage would eventually break
MATCH (impacted:Service)-[:DEPENDS_ON*1..]->(failing:Service {name: 'auth'})
RETURN DISTINCT impacted.name AS impactedService

Single points of failure. The same Betweenness Centrality used to find bridge accounts above finds bridge services: the service with the highest score sits on the most dependency paths and is the one outage that would fragment the architecture the most.

CALL gds.betweenness.stream('services')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS service, score
ORDER BY score DESC
LIMIT 5

Dependency-cycle validation. A healthy dependency graph should be acyclic; a cycle means two or more services depend on each other, directly or indirectly, and neither can safely start, deploy, or roll back independently. Strongly Connected Components finds exactly this: any component with more than one member is a cycle.

CALL gds.scc.stream('services')
YIELD nodeId, componentId
WITH componentId, collect(gds.util.asNode(nodeId).name) AS services
WHERE size(services) > 1
RETURN componentId, services

See Betweenness Centrality and Strongly Connected Components. A dedicated treatment of dependency-graph root-cause analysis exists — see the bibliography.

Tying it together

All five scenarios above are special cases of the same underlying pattern: model the domain as a property graph, project the relevant slice into GDS, and let similarity, community-detection, centrality or pathfinding algorithms surface structure that would otherwise take hand-rolled recursive queries or a separate graph-processing system to find. That is also exactly the toolbox behind general master-data-management and knowledge-graph programs — entity resolution to keep one canonical node per customer, product or organizational unit; community detection and centrality to understand how that canonical graph is actually connected; and weighted pathfinding wherever a canonical record needs a shortest or cheapest route to another. The five use cases above are simply that same toolbox applied to five concrete business questions.

See also Graph Data Science Fundamentals for the graph-projection groundwork every example on this page builds on.