Acyclic paths

By default, Cypher® only prevents relationships being repeated in a MATCH result, not nodes (see Paths with unique relationships for more information). The ACYCLIC keyword adds a further restriction by not allowing nodes to be repeated within a path, though they can still be repeated across paths enabling the formation of equijoins.

The ACYCLIC keyword must be specified before a path pattern:

Query using ACYCLIC
MATCH p = ACYCLIC (:Router {name: 'A'})-[:LINK]-+(:Router {name: 'Z'})
RETURN p
ACYCLIC is a GQL conformant path mode. Path modes are levels of constraints that determine whether nodes or relationships can appear more than once in a path pattern match. Cypher also supports explicitly specifying the GQL path modes TRAIL and WALK though these do not alter the constraints already imposed by DIFFERENT RELATIONSHIPS or REPEATABLE ELEMENTS respectively. For more information, see Syntax and semantics → Path modes.

Example graph

To illustrate the practical application of ACYCLIC pruning, consider a router network used to model packet forwarding. In networking, identifying loop-free transmission routes is critical for avoiding broadcast storms and network congestion. The following graph contains several routing loops and bidirectional links between routers.

To recreate it, run the following query against an empty Neo4j database:

CREATE
  (a:Router {name: 'A'}), (b:Router {name: 'B'}), (c:Router {name: 'C'}),
  (d:Router {name: 'D'}), (e:Router {name: 'E'}), (f:Router {name: 'F'}),
  (g:Router {name: 'G'}), (h:Router {name: 'H'}), (i:Router {name: 'I'}),
  (j:Router {name: 'J'}), (k:Router {name: 'K'}), (z:Router {name: 'Z'}),

  (a)-[:LINK]->(b), (a)-[:LINK]->(c), (a)-[:LINK]->(d), (c)-[:LINK]->(b),
  (c)-[:LINK]->(d), (b)-[:LINK]->(e), (c)-[:LINK]->(e), (d)-[:LINK]->(f),
  (e)-[:LINK]->(g), (f)-[:LINK]->(g), (h)-[:LINK]->(i), (h)-[:LINK]->(j),
  (i)-[:LINK]->(j), (i)-[:LINK]->(z), (g)-[:LINK]->(j), (g)-[:LINK]->(k),
  (j)-[:LINK]->(k), (j)-[:LINK]->(z), (k)-[:LINK]->(z)

Relationship uniqueness in packet forwarding

In many graph contexts, the default relationship uniqueness mode of Cypher is sufficient. However, in packet forwarding, a packet should not visit the same router twice, even if it uses a different link. Relationship uniqueness only ensures that no relationship is reused — it does not prevent nodes from being revisited, meaning cycles are still possible. In the router network graph, for example, there are paths where no relationships are repeated but nodes are revisited, such as the cycles involving Router B, C, and D.

For example, the following query uses Cypher’s default behavior, which disallows repeated relationships but allows repeated nodes:

Find cyclic paths (with repeated routers) from 'A' to 'Z', using inline predicates to exclude start and target nodes
MATCH p = (:Router {name: 'A'})
          ((l WHERE l.name <> 'Z')-[:LINK]-(m WHERE m.name <> 'A'))+
          (:Router {name: 'Z'}) (1)
WHERE size(nodes(p)) > COUNT { UNWIND nodes(p) AS n RETURN DISTINCT n } AND length(p) = 8 (2)
RETURN [n IN nodes(p) | n.name] AS routeWithCycles (3)
ORDER BY length(p), p LIMIT 1
1 A quantified path pattern with inline predicates excluding paths that either return to the start router or pass through the target router.
2 Filter for paths that have cycles, where the number of nodes in the path is greater than the number of distinct nodes in the path.
3 List comprehension converting the sequence of nodes to a compact list of their names.
Result
routeWithCycles

["A", "B", "E", "G", "J", "I", "H", "J", "Z"]

Rows: 1

Notice how the J node is visited twice.

ACYCLIC forwarding paths

To ensure that no node is repeated, or, in this case, prevent packet forwarding that involves revisiting the same routers, use the ACYCLIC keyword.

MATCH p = ACYCLIC (:Router {name: 'A'})-[:LINK]-+(:Router {name: 'Z'})
RETURN [n IN nodes(p) | n.name] AS routeWithoutCycles
ORDER BY p LIMIT 1

Note that, with ACYCLIC, the predicates to exclude visiting the start or target nodes are redundant.

Result
routeWithoutCycles

["A", "B", "E", "G", "J", "I", "Z"]

Rows: 1

You can also use ACYCLIC to examine the number of possible routes from the start to the target router that pass through each of the intermediate routers. By grouping the paths based on the intermediate routers and calculating the percentage of total paths that include each router, it is possible to determine the network’s sensitivity to a failure at a particular router.

Determining the percentage of acyclic paths from 'A' to 'Z' that pass through each intermediate router
LET middleNodesOfPaths = COLLECT {
  MATCH p = ACYCLIC (start:Router {name: 'A'})-[:LINK]-+(end:Router {name: 'Z'}) (1)
  RETURN nodes(p)[1..-1] (2)
}
LET numPaths = size(middleNodesOfPaths) (3)
UNWIND coll.flatten(middleNodesOfPaths) AS node (4)
WITH node.name AS node, count(*) AS numPathsWithNode, numPaths (5)
RETURN node, round(100.0 * numPathsWithNode / numPaths, 1) AS `% of paths with node`
ORDER BY numPathsWithNode DESC
1 Collects all the ACYCLIC paths to count the total number of paths from 'A' to 'Z'.
2 List slicing is used to remove the first and last node from each path.
3 The number of lists of middle nodes equals the number of paths.
4 The coll.flatten() function unnests middleNodesOfPaths (a list of lists) into a single list of nodes.
5 As the paths are ACYCLIC, counting the number of times a particular node appears in the list of lists tells you in how many paths that node appears.
Result
"node" "% of paths with node"

"G"

100.0

"J"

87.5

"C"

80.0

"E"

70.0

"K"

62.5

"D"

60.0

"B"

60.0

"I"

50.0

"F"

40.0

"H"

25.0

Rows: 10

This shows that router G is the weakest point in the network, with 100% of traffic from A to Z passing through it.

Performance improvement with ACYCLIC

Using ACYCLIC is significantly more efficient than using manual post-filters for node uniqueness.

Prior to the release of the ACYCLIC, Cypher would return paths satisfying the pattern, including those with nodes repeated. Only after the generation of these paths could a WHERE filter discard those containing cycles. For the below query, the following is true:

  • Evaluation: The engine generates every possible path, including those that loop infinitely or visit the same nodes multiple times.

  • Overhead: The engine must compute and hold a massive set of cyclic solutions in memory before the filter can discard them.

Using WHERE to discard paths containing cycles
MATCH p = (start:User {id: 1})-[:FRIEND]->+(target:User)
  WHERE size(nodes(p)) = COUNT { UNWIND nodes(p) AS n RETURN DISTINCT n }
RETURN p

However, ACYCLIC applies the uniqueness constraint during evaluation. This produces a significantly more efficient query. More specifically:

  • Evaluation: As the engine traverses the graph, it monitors the nodes already present in the path.

  • Pruning: If the engine attempts to move to a node that has already been visited in the current path, it prunes that specific candidate solution immediately.

  • Efficiency: Because invalid paths are never fully generated or held in memory, execution is faster and uses fewer resources.

Applying uniqueness constraints during query evaluation with ACYCLIC
MATCH p = ACYCLIC (start:User {id: 1})-[:FRIEND]->+(target:User)
RETURN p