Chapter 04 · Advanced Pattern Matching: Variable Length, OPTIONAL MATCH, Paths, Quantified Patterns, and Shortest Paths

Variable-Length and Quantified Path Patterns: Bounds, Semantics, and Combinatorial Risk

Treat multi-hop matching as a candidate-path search whose cost is shaped by fan-out, depth, cycles, direction, types, anchors and predicates—not as a free recursive lookup.

Intermediate120–145 minutesQuantified path/cardinality labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

AtlasMart wants to answer questions such as “which operational points can a supplier reach within four handoffs?” That sounds simple until branching and cycles turn one start node into many path candidates. This lesson builds the path mental model before the graph becomes large enough to hide mistakes.

01

Distinguish a path pattern from the concrete path values that satisfy it.

02

Use finite bounds with legacy variable-length relationships and current quantified relationships.

03

Explain why fan-out and depth multiply candidate paths, especially in cyclic graphs.

04

Read EXPLAIN/PROFILE cardinality evidence without treating operator names as permanent contracts.

05

Rewrite broad path enumeration with selective anchors, relationship types, bounds and inline predicates.

Chapter 04 continuity contract

Continue Chapters 01–03 with Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 in version-sensitive examples, local container atlasmart-neo4j, Bolt 127.0.0.1:7687, HTTP 127.0.0.1:7474, and constraint-backed AtlasMart domain identifiers. Chapter 04 adds a small synthetic operational handoff subgraph on existing Supplier/Category/Store concepts; it is intentionally isolated for path-mechanics exercises and does not replace the transactional relationships modeled earlier.

Version and execution note

Neo4j 2026.07.1 is the current 2026 release used by this course snapshot; 5.26.30 remains the current 5.26 LTS comparison line. The current manual covers Cypher 25; Cypher 5 is frozen. Quantified path patterns/relationships date from Neo4j 5.9, while explicit Cypher 25 path modes such as ACYCLIC arrived later and have version-sensitive combination rules. Commands here were checked against current documentation but could not be executed in this generation environment, so expected output is described by deterministic invariants rather than fabricated captures.

1. Variable length means “many candidate paths,” not “one recursive lookup”

A path is an ordered alternating sequence of nodes and relationships. A path pattern describes which paths are acceptable. With a fixed-length pattern, each relationship position is explicit. With a variable-length or quantified pattern, one textual fragment may represent many path lengths and many concrete paths. The number of results therefore depends on graph degree, direction, relationship type, predicates, and the allowed depth—not only on the number of nodes.

Form Example Use
Fixed length (a)-[:HANDOFF_TO]->(b) Exactly one relationship.
Legacy variable-length -[:HANDOFF_TO*1..4]-> Still supported; familiar to Cypher 5 users but not GQL-conformant.
Quantified relationship -[:HANDOFF_TO]->{{1,4}} Current concise form for one repeated relationship pattern.
Quantified path pattern ((a)-[r:HANDOFF_TO]->(b)){{1,4}} Repeats a richer node/relationship fragment and exposes group variables.

2. Build the cyclic fixture, then count by bounded depth

Cypher · idempotent cyclic AtlasMart handoff fixture
CYPHER 25CREATE CONSTRAINT supplier_id IF NOT EXISTS FOR (s:Supplier) REQUIRE s.supplierId IS UNIQUE;CREATE CONSTRAINT category_id IF NOT EXISTS FOR (c:Category) REQUIRE c.categoryId IS UNIQUE;CREATE CONSTRAINT store_id IF NOT EXISTS FOR (s:Store) REQUIRE s.storeId IS UNIQUE;CREATE CONSTRAINT ops_point_id IF NOT EXISTS FOR (n:OpsPoint) REQUIRE n.pointId IS UNIQUE;MERGE (s1:Supplier {supplierId:'SUP-3001'}) SET s1:OpsPoint, s1.pointId='OP-SUP-1', s1.name='Northwind Optics';MERGE (s2:Supplier {supplierId:'SUP-3002'}) SET s2:OpsPoint, s2.pointId='OP-SUP-2', s2.name='Audio Forge';MERGE (c1:Category {categoryId:'CAT-CAMERAS'}) SET c1:OpsPoint, c1.pointId='OP-CAT-CAM', c1.name='Cameras';MERGE (c2:Category {categoryId:'CAT-AUDIO'}) SET c2:OpsPoint, c2.pointId='OP-CAT-AUD', c2.name='Audio';MERGE (st1:Store {storeId:'ST-001'}) SET st1:OpsPoint, st1.pointId='OP-ST-1', st1.name='Central', st1.region='west';MERGE (st2:Store {storeId:'ST-002'}) SET st2:OpsPoint, st2.pointId='OP-ST-2', st2.name='Harbor', st2.region='east';MERGE (st3:Store {storeId:'ST-003'}) SET st3:OpsPoint, st3.pointId='OP-ST-3', st3.name='Airport', st3.region='north';MATCH (s1:OpsPoint {pointId:'OP-SUP-1'}), (s2:OpsPoint {pointId:'OP-SUP-2'}),      (c1:OpsPoint {pointId:'OP-CAT-CAM'}), (c2:OpsPoint {pointId:'OP-CAT-AUD'}),      (st1:OpsPoint {pointId:'OP-ST-1'}), (st2:OpsPoint {pointId:'OP-ST-2'}), (st3:OpsPoint {pointId:'OP-ST-3'})MERGE (s1)-[:HANDOFF_TO {routeId:'R01', minutes:30, active:true}]->(st1)MERGE (s1)-[:HANDOFF_TO {routeId:'R02', minutes:10, active:true}]->(c1)MERGE (c1)-[:HANDOFF_TO {routeId:'R03', minutes:8, active:true}]->(st1)MERGE (c1)-[:HANDOFF_TO {routeId:'R04', minutes:5, active:true}]->(c2)MERGE (st1)-[:HANDOFF_TO {routeId:'R05', minutes:12, active:true}]->(s2)MERGE (st1)-[:HANDOFF_TO {routeId:'R06', minutes:11, active:false}]->(c2)MERGE (s2)-[:HANDOFF_TO {routeId:'R07', minutes:9, active:true}]->(c2)MERGE (s2)-[:HANDOFF_TO {routeId:'R08', minutes:6, active:true}]->(st2)MERGE (c2)-[:HANDOFF_TO {routeId:'R09', minutes:7, active:true}]->(st2)MERGE (st2)-[:HANDOFF_TO {routeId:'R10', minutes:14, active:true}]->(s1)MERGE (st2)-[:HANDOFF_TO {routeId:'R11', minutes:4, active:true}]->(st3)MERGE (st3)-[:HANDOFF_TO {routeId:'R12', minutes:13, active:true}]->(c1);
Cypher · bounded path counts from one supplier
CYPHER 25MATCH (s:OpsPoint {pointId:'OP-SUP-1'})CALL (s) {  MATCH p=(s)-[:HANDOFF_TO]->{1,2}(:OpsPoint)  RETURN count(p) AS upTo2}CALL (s) {  MATCH p=(s)-[:HANDOFF_TO]->{1,4}(:OpsPoint)  RETURN count(p) AS upTo4}CALL (s) {  MATCH p=(s)-[:HANDOFF_TO]->{1,6}(:OpsPoint)  RETURN count(p) AS upTo6}RETURN upTo2, upTo4, upTo6;

The exact counts are deterministic for this fixture but are intentionally not hard-coded here because the generation environment did not execute Neo4j. The invariant to prove is monotonic growth: allowing more depth cannot reduce the set of matching paths. On a branching cyclic graph, the increase can be much faster than the node count suggests.

3. Quantified path patterns can prune while traversing

Current quantified path patterns allow predicates inside the repeated fragment. That is more useful than enumerating every path first and filtering afterward because the engine can reject candidate extensions during traversal.

Cypher · only traverse active handoffs toward the east store
CYPHER 25MATCH (s:OpsPoint {pointId:$source}),      (target:Store:OpsPoint {storeId:$storeId})MATCH p=(s)        ((a:OpsPoint)-[r:HANDOFF_TO]->(b:OpsPoint)          WHERE r.active = true){1,5}        (target)RETURN length(p) AS hops,       [n IN nodes(p) | n.pointId] AS points,       [r IN relationships(p) | r.routeId] AS routesORDER BY hops, points;

The predicates belong inside the quantified fragment because they describe which relationship extensions are legal. Bounds remain essential even when predicates are selective: a future data change can increase degree or create new cycles.

4. Make planning and execution evidence explicit

Cypher · compare structure without and with execution
CYPHER 25 EXPLAINMATCH p=(:OpsPoint {pointId:'OP-SUP-1'})-[:HANDOFF_TO]->{1,5}(:OpsPoint)RETURN count(p);CYPHER 25 PROFILEMATCH p=(:OpsPoint {pointId:'OP-SUP-1'})-[:HANDOFF_TO]->{1,5}(:OpsPoint)RETURN count(p);

EXPLAIN does not execute; use it to inspect estimated cardinality and access choices. PROFILE executes and adds actual row/db-hit/memory evidence. Operator names and estimates can change by release/runtime, so record the server/Cypher version and interpret the trend rather than teaching one plan screenshot as universal truth.

5. Deliberately wrong approach: “no upper bound because the graph is small”

Do not run an open-ended broad traversal on an unknown dense production graph.

Under the default match semantics a relationship is not repeated within one matched result, so the search space is finite for a finite graph, but it can still be combinatorially huge. A cycle does not make the query magically safe.

Cypher · safer bounded rewrite
CYPHER 25MATCH (s:OpsPoint {pointId:$source})MATCH p=(s)-[:HANDOFF_TO]->{1,4}(target:Store:OpsPoint)WHERE target.region=$regionRETURN target.storeId, min(length(p)) AS minimumHopsORDER BY minimumHops, target.storeId;

Production judgment starts with a question contract: maximum meaningful depth, acceptable timeout, expected degree distribution, target selectivity and whether the application needs all paths, one path, existence, or only the nearest result. The next lesson shows another cardinality trap: optional patterns that preserve rows until a predicate is placed in the wrong scope.

Check your understanding

  1. Why can a six-hop bound be expensive even on a graph with only a few node labels?
  2. What is the main semantic difference between a quantified path pattern and a single fixed relationship?
  3. Why is an inline predicate valuable inside a quantified pattern?
  4. Does default Cypher path matching allow the same relationship to be traversed repeatedly within one matched path?
  5. What does PROFILE add beyond EXPLAIN?
Review the answers

1. Fan-out compounds at each depth and cycles create many alternative paths even when the number of node categories is small.

2. The quantified pattern can repeat a path fragment across a range, yielding many concrete paths and group-variable lists.

3. It can prune illegal extensions during traversal instead of generating every candidate first and filtering later.

4. No; the default behavior does not repeat a relationship within a matched result, though nodes may repeat.

5. PROFILE executes the query and adds actual row/db-hit/memory/runtime evidence; EXPLAIN is planning-only.

Summary and next step

Variable-Length and Quantified Path Patterns: Bounds, Semantics, and Combinatorial Risk is useful only when its assumptions and observed evidence stay attached to the decision. The examples above establish a reproducible mechanism and boundary; they do not turn one lab result into a universal production rule.

Next, continue to OPTIONAL MATCH and Null Introduction: Preserving Rows Without Accidentally Filtering Them Away. Carry forward the verified assumptions, fixture state, version/edition boundaries, and measurements from this lesson instead of treating the next topic as an isolated recipe.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.