Shortest Paths in Neo4j

Introduction

Welcome back to Advanced Queries! In the previous lesson, we explored how to traverse paths of varying lengths through the graph, finding all connections within a certain distance.

While finding all possible paths between two points is valuable for understanding network structure, we often face a simpler question: What is the most direct route? Whether we're analyzing social networks, planning routes, or tracing dependencies, knowing the shortest path between two nodes is frequently more useful than enumerating every possible connection.

In this unit, we'll learn how to efficiently find the shortest path between any two nodes using Cypher's built-in shortestPath() function. We'll discover how to extract complete routes showing the chain of connections, measure the exact distance between nodes, and understand when this technique is the right tool for the job. These skills are essential for network analysis, recommendation systems, and understanding the structure of connected data.

The Shortest Path Problem

Finding the shortest path between two points is one of the classic problems in graph theory. In a social network, the shortest path represents the minimum degrees of separation between two people. In a transportation network, it might represent the route with the fewest transfers. In a dependency graph, it shows the most direct chain of relationships.

To understand the problem, consider a small social network where Alice and Bob are connected through multiple routes. As you can see in the visualization below, there are several paths connecting them, each with a different number of hops.

The key insight is that, while there might be many paths connecting two nodes, we often care most about the most efficient one. This path uses the fewest hops, representing the most direct connection. For example, if Alice knows Bob through Carol in just two hops, that connection is more meaningful than a circuitous route through five intermediate friends.

Neo4j provides the shortestPath() function specifically for this purpose. Unlike variable-length patterns that return all matching paths, shortestPath() efficiently finds just one path with the minimum number of hops. This focused approach is both faster and more practical for many real-world applications.

The shortestPath Function

The shortestPath() function wraps a variable-length pattern and returns a single path with the minimum number of relationships. Here's the basic syntax:

shortestPath((start)-[:RELATIONSHIP_TYPE*]-(end))

The function takes a variable-length pattern as its argument. Notice three important aspects: First, we must specify which start and end nodes to connect by binding them with labels and properties within the pattern; second, the relationship pattern must use variable-length syntax with the asterisk; third, the function returns a Path object, just like the path variables we worked with in the previous lesson.

The power of shortestPath() lies in its efficiency. Instead of exploring all possible paths and then filtering to the shortest, it uses optimized algorithms internally to find the minimum-hop path directly.

Finding the Shortest Route

Let's find the shortest path between Alice and Bob:

MATCH path = shortestPath(
  (alice:User {name: 'Alice'})-[:FRIENDS_WITH*]-(bob:User {name: 'Bob'})
)
RETURN path

This query performs several operations. First, it binds our start and end nodes using their names. Second, it calls shortestPath() with an undirected, unbounded variable-length pattern. Third, it returns the complete path object showing the full route from Alice to Bob with the fewest hops.

Notice we bind the result to a variable called path, allowing us to use path functions like nodes() to extract detailed information, or simply return the path to visualize the complete route with all node properties and relationships.

Examining the Output

When we run our shortest path query, we see the complete path visualization:

path
(:User {name: "Alice", city: "New York", age: 28})-[:FRIENDS_WITH]->(:User {name: "Carol", city: "New York", age: 25})-[:FRIENDS_WITH]->(:User {name: "Bob", city: "San Francisco", age: 32})

This output shows that the shortest route from Alice to Bob goes through Carol, requiring two hops. Even if there are other paths connecting Alice and Bob, perhaps through other mutual friends, this is the most direct route. The function has efficiently found the path with the minimum number of relationships.

The result format shows the complete chain of connections with full node properties, making it easy to see not just who connects Alice and Bob, but also additional context like their locations and ages. We can immediately see that Alice and Bob are not direct friends, but they share a mutual friend, Carol, who connects them.

Measuring Distance

Sometimes, we don't need the actual route, just the distance. We can use the length() function to count the number of hops:

MATCH path = shortestPath(
  (a:User {name: 'Alice'})-[:FRIENDS_WITH*]-(b:User {name: 'Bob'})
)
RETURN length(path) AS degrees

The length() function returns the number of relationships in the path. In our social network example, this number represents the minimum degrees of separation between two people. In social network analysis, this metric is often more important than knowing the specific intermediate connections.

We still need to bind the path to a variable even though we're only interested in its length. The shortestPath() function returns the complete path object, and then we extract just the distance information we need.

Understanding Degrees of Separation

The output from our distance query gives us a precise measurement:

degrees
2

This result confirms that Alice and Bob are exactly two degrees apart. They're friends of friends, connected through one intermediate person. This concept of degrees of separation is fundamental in social network analysis; it measures how closely connected people are without requiring us to know the specific individuals linking them.

The measurement is particularly useful for analytics and recommendations. For instance, we might prioritize recommendations differently for people who are two degrees away versus five degrees away, treating closer connections as more relevant.

When to Use Shortest Paths

The shortestPath() function excels in several common scenarios. In social networks, it helps measure influence propagation and suggest the most likely connection path between users. In knowledge graphs, it can explain relationships between concepts by showing the shortest semantic chain. In dependency systems, it reveals direct impact paths between components.

The function is ideal when you need to answer questions like: "What's the most direct route?", "How closely connected are these entities?", or "What's the minimum distance between two points?" It's particularly valuable in recommendation systems, where showing how two people are connected makes recommendations more compelling and trustworthy.

However, shortestPath() is limited to counting hops; it doesn't consider weights or costs. If relationships have varying importance or cost, you'll need weighted shortest path algorithms like Dijkstra's algorithm, which are available through Neo4j's Graph Data Science library.

Performance and Best Practices

While shortestPath() is optimized internally, we should still follow best practices for efficient queries. Always bind both start and end nodes before calling the function; searching from every possible start to every possible end would be catastrophically expensive. Use labels and indexed properties to make node lookups fast.

Specifying relationship types and directions also improves performance. A pattern like [:FRIENDS_WITH*] is more selective than [*], reducing the search space. Although we can use unbounded patterns inside shortestPath(), being explicit about reasonable maximum distances (like *..10) provides a safety net.

The function is designed to find a single shortest path. If you need all shortest paths of equal length, use allShortestPaths() instead, but be aware that this can return many results on dense graphs. For most applications, one representative shortest path is sufficient.

Conclusion and Next Steps

In this final lesson of our course, we've mastered Neo4j's shortestPath() function for finding the most direct connections between nodes. We learned how to extract complete routes showing chains of relationships, measure distances to understand degrees of separation, and recognize when shortest path analysis is the right approach for our problem.

This capability completes our toolkit for advanced graph queries. Combined with aggregation, pattern matching, and multi-hop traversals from previous units, you now have the skills to analyze complex graph structures, understand network connectivity, and extract meaningful insights from connected data.

Now it's time to solidify these concepts through hands-on practice! The exercises ahead will give you the opportunity to write your own shortest path queries, measure network distances, and apply these techniques to real-world scenarios. Let's see how well you can navigate the shortest routes through graph data!

Note: The query outputs shown throughout this lesson (such as results with Alice, Bob, Carol, etc.) come from the practice database you'll work with in the exercises. The visualizations are simplified diagrams to illustrate concepts.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal