Pattern Matching in Cypher
Pattern matching syntax
Cypher’s central operation is pattern matching. A MATCH clause describes a subgraph pattern, and the query engine finds all occurrences in the stored graph. Patterns use ASCII-art syntax to represent nodes and edges.
Basic patterns
| Pattern | Meaning |
|---|---|
(a)-->(b) | Any directed edge from a to b |
(a:Person)--> | Any edge from a node of type Person |
(b:Movie) | Any node of type Movie |
(a)-[:ACTED_IN]->(b) | An ACTED_IN edge from a to b |
()-->(a)<--() | Nodes with two or more incoming edges |
(a)--(b) | Edges in either direction (undirected match) |
The (a)-->(b) form is the simplest: it matches any node connected to any other node by a single directed edge. The (a:Person)-->(b:Movie) form adds type constraints.
Variable-length paths (Kleene star)
Cypher supports transitive matching using the Kleene star (zero or more steps):
(a:Person)-[:ACTED_IN*]-(b)
This matches paths of any length where every edge has type ACTED_IN. The * operator is analogous to the Kleene closure operation from formal languages: it matches zero, one, or more repetitions of the edge pattern.
Bounded paths use the [*min..max] syntax:
(a)-[*3..5]->(b)
This matches paths of length 3 to 5 (inclusive). Useful bounds prevent runaway search on large graphs.
Shortest path qualification:
(a)-[:ACTED_IN*..6]-(b)
This matches paths of length up to 6 (with *..6 as shorthand for *0..6).
Friend-of-a-friend example
MATCH (john {name: 'John'})-[:FRIEND]->()-[:FRIEND]->(fof)
RETURN john.name, fof.name
This query finds all nodes fof reachable from John by exactly two FRIEND edges. The empty () in the middle matches any intermediate node — it is a wildcard for the friend of John who is also the friend of fof.
The result set contains one row per unique (john, fof) pair that is connected by a two-step path. If there are multiple mutual friends connecting John to the same fof, that fof appears only once (because MATCH returns distinct pattern matches, not every path).
Co-actors query
Actors who have appeared in the same film as each other:
MATCH (p1:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person)
WHERE p1.person_id <> p2.person_id
RETURN p1.name, p2.name, count(*) AS total
ORDER BY total DESC
LIMIT 10
This pattern matches a pair of people both linked by ACTED_IN edges to the same movie node. The WHERE clause excludes self-pairs (the same person matched twice).
Equivalent formulations of the same query
The co-actors query can be written in several equivalent ways:
Form 1 — Two separate patterns (conjunction):
MATCH (p1:Person)-[:ACTED_IN]->(m:Movie),
(p2:Person)-[:ACTED_IN]->(m:Movie)
WHERE p1.person_id <> p2.person_id
Form 2 — Chained pattern (as above):
MATCH (p1:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person)
WHERE p1.person_id <> p2.person_id
Form 3 — Two-hop transitive match:
MATCH (p1:Person)-[:ACTED_IN*2]-(p2:Person)
WHERE p1.person_id <> p2.person_id
Form 3 is semantically different: it matches any two-step path where both edges are ACTED_IN, but it does not require that both edges point to the same movie. An actor could be connected to another actor by acting in Movie A and the second actor acting in Movie B — this would also be a two-hop path, but they are not co-stars. Form 3 overcounts.
Return and aggregation
RETURN can include aggregate functions (count, sum, avg, collect). When aggregation is present, non-aggregated columns form implicit grouping keys — the same semantics as SQL’s GROUP BY on all non-aggregated columns.
Summary
- Cypher patterns use
(node)-[edge]->(node)ASCII-art syntax to describe subgraph structures. - Type constraints use colon syntax:
(a:Person),[:ACTED_IN]. - The Kleene star
*enables transitive matching; bounds are written as[*min..max]. - The same graph query can often be expressed in multiple ways with different semantics.
- Two-hop paths via the same node require an intermediate variable, not just
*2.