OpenCypher Pattern Matching¶
This document provides a comprehensive reference for all pattern matching features in the OpenCypher specification. Pattern matching is the core mechanism for navigating, describing, and extracting data from graph databases using declarative patterns.
OpenCypher Reference: https://opencypher.org/
Neo4j Cypher Manual: https://neo4j.com/docs/cypher-manual/current/patterns/
Table of Contents¶
- Pattern Fundamentals
- Node Patterns
- Relationship Patterns
- Path Patterns
- Variable-Length Patterns
- Pattern Predicates
- Optional Patterns
- Pattern Comprehension
- Multiple Patterns
- Pattern Matching Semantics
- Pattern Performance Considerations
Pattern Fundamentals¶
Graph pattern matching sits at the very core of Cypher. It uses a visual notation that mirrors whiteboard diagrams:
- Nodes are represented as parentheses:
() - Relationships are represented as dashes and arrows:
-->,<--, or--
Basic Pattern Syntax:
Purpose: Patterns enable declarative graph queries without explicit algorithmic specification. You describe what structure you want to match, and the database engine determines how to find it.
Key Principle: Patterns are used primarily in MATCH clauses and pattern-based subqueries (EXISTS, COUNT, COLLECT).
Node Patterns¶
Node patterns match individual nodes in the graph and can specify labels, properties, and variables.
Syntax¶
Basic Node Patterns¶
Empty node pattern - Matches any node:
Node with variable - Binds matched node to a variable:
Anonymous node - Matches node without binding to variable:
Node Patterns with Labels¶
Single label - Matches nodes with specific label:
Multiple labels (conjunction) - Matches nodes with ALL specified labels:
Alternative syntax using &:
Label disjunction - Matches nodes with ANY of the specified labels:
Label negation - Matches nodes WITHOUT specific label:
Complex label expressions - Combines operators for advanced matching:
// Matches labeled nodes that are not Person
MATCH (n:!Person&%)
RETURN n
// Matches nodes with (A OR B) AND NOT C
MATCH (n:(A|B)&!C)
RETURN n
Label Expression Operators:
| Operator | Meaning | Precedence |
|----------|---------|-----------|
| % | Wildcard (any non-empty label set) | 1 |
| () | Grouping | 1 |
| ! | Negation | 2 |
| & | Conjunction (AND) | 3 |
| \| | Disjunction (OR) | 4 |
Node Patterns with Properties¶
Single property match - Matches nodes with specific property value:
Multiple properties - All properties must match (conjunction):
Property expressions - Use computed values:
Empty property map - Matches nodes without filtering by properties:
Node Patterns with WHERE Clause¶
Inline WHERE predicate - Filters during pattern matching:
Complex predicates - Use functions and expressions:
MATCH (m:Movie WHERE m.year >= 2000 AND m.rating > 7.5)
RETURN m.title, m.year, m.rating
ORDER BY m.rating DESC
Common Node Pattern Use Cases¶
-
Finding nodes by type:
-
Filtering by properties:
-
Complex filtering with WHERE:
-
Multiple label requirements:
Relationship Patterns¶
Relationship patterns connect nodes and specify direction, type, properties, and traversal constraints.
Syntax¶
-[ variable ][ :type ][ properties ][ WHERE clause ]->
<-[ variable ][ :type ][ properties ][ WHERE clause ]-
-[ variable ][ :type ][ properties ][ WHERE clause ]-
Basic Relationship Patterns¶
Directed relationship (right) - Matches relationships pointing right:
Directed relationship (left) - Matches relationships pointing left:
Undirected relationship - Matches relationships in either direction:
Relationship with variable - Binds relationship to variable:
Relationship Patterns with Types¶
Single type - Matches relationships of specific type:
Multiple types (disjunction) - Matches ANY of the specified types:
MATCH (person:Person)-[:ACTED_IN|:DIRECTED]->(movie:Movie)
RETURN person.name, type(r) AS role, movie.title
Type with variable:
Relationship Patterns with Properties¶
Single property match:
Multiple properties:
MATCH (user)-[r:PURCHASED {status: 'completed', payment_method: 'credit_card'}]->(product)
RETURN user.name, product.name, r.amount
Property constraints with WHERE:
MATCH (a)-[r:KNOWS WHERE r.since < 2000]->(b)
RETURN a.name AS person, b.name AS friend, r.since
ORDER BY r.since
Directional Pattern Variations¶
Pattern 1: Left to right:
Pattern 2: Right to left:
Pattern 3: Bidirectional (either direction):
Pattern 4: Chain of relationships:
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN a.name, b.name, c.name
Common Relationship Pattern Use Cases¶
-
Finding related entities:
-
Multiple relationship types:
-
Relationship property filtering:
-
Bidirectional friendships:
Path Patterns¶
Path patterns match sequences of nodes and relationships as cohesive units, enabling path-level operations and analysis.
Syntax¶
Basic Path Patterns¶
Simple path assignment:
Path with multiple relationships:
Path with mixed relationship types:
MATCH path = (actor:Person)-[:ACTED_IN]->(movie:Movie)<-[:DIRECTED]-(director:Person)
RETURN path, nodes(path), relationships(path)
Path Functions¶
Length of path - Number of relationships in path:
MATCH path = (a:Person)-[:KNOWS*]->(b:Person)
WHERE a.name = 'Alice' AND b.name = 'Bob'
RETURN path, length(path) AS degrees_of_separation
ORDER BY length(path)
LIMIT 1
Nodes in path - Extract all nodes:
MATCH path = (a:Person)-[:KNOWS*1..3]->(b:Person)
WHERE a.name = 'Alice'
RETURN [node IN nodes(path) | node.name] AS path_names
Relationships in path - Extract all relationships:
MATCH path = (a)-[*]->(b)
WHERE a.name = 'Start' AND b.name = 'End'
RETURN [rel IN relationships(path) | type(rel)] AS relationship_types
Path Pattern Use Cases¶
-
Finding shortest path:
-
Analyzing path properties:
-
Extracting path information:
-
Path existence checking:
Variable-Length Patterns¶
Variable-length patterns match paths of unknown or varying lengths using quantifiers and repetition syntax.
Quantified Path Patterns¶
Modern GQL-conformant syntax:
One or more repetitions (+ quantifier):
Zero or more repetitions (* quantifier):
Exact repetition count:
Bounded repetition range:
MATCH (a:Person) ((()-[:KNOWS]->())){1,3} (b:Person)
WHERE a.name = 'Alice'
RETURN b.name, count(*) AS path_count
Quantified Relationships¶
Simplified syntax for single relationships:
Examples:
// 1 to 3 hops
MATCH (a:Person)-[:KNOWS]->{1,3}(b:Person)
RETURN a.name, b.name
// Exactly 2 hops
MATCH (a)-[:FOLLOWS]->{2}(b)
RETURN a, b
// At least 2 hops (unbounded)
MATCH (a)-[:PARENT_OF]->{2,}(descendant)
RETURN a.name AS ancestor, descendant.name
Legacy Variable-Length Syntax¶
Asterisk notation (older syntax, still supported):
-[*]-> // One or more hops
-[*1..3]-> // 1 to 3 hops
-[*..5]-> // Up to 5 hops
-[*2..]-> // 2 or more hops
Examples with legacy syntax:
// Variable-length with type
MATCH (a:Person)-[:KNOWS*1..3]->(b:Person)
WHERE a.name = 'Alice'
RETURN DISTINCT b.name
// Unbounded traversal (use with caution)
MATCH (root:Category {name: 'Electronics'})-[:SUBCATEGORY*]->(leaf:Category)
WHERE NOT EXISTS { (leaf)-[:SUBCATEGORY]->() }
RETURN leaf.name AS leaf_category
// Variable-length with relationship variable
MATCH (a:City)-[roads:ROAD*1..5]->(b:City)
WHERE a.name = 'New York' AND b.name = 'Boston'
RETURN reduce(distance = 0, r IN roads | distance + r.miles) AS total_distance
ORDER BY total_distance
LIMIT 1
Group Variables¶
Variables declared inside quantified patterns become group variables (lists):
MATCH (a:Person) ((l:Person)-[r:KNOWS]->(m:Person)){1,3} (b:Person)
WHERE a.name = 'Alice'
RETURN a.name,
[node IN l | node.name] AS intermediate_people,
[rel IN r | rel.since] AS relationship_years,
b.name
Key behavior:
- l, r, m are lists containing all matched elements across repetitions
- Enables aggregation across path segments
- Useful for property accumulation and filtering
Variable-Length Pattern Use Cases¶
-
Social network degrees of separation:
-
Organizational hierarchy traversal:
-
Category tree navigation:
-
Finding all reachable nodes:
-
Path accumulation with group variables:
Performance Considerations¶
Inline predicates - Filter during traversal to prevent path explosion:
MATCH (a:Person) ((:Person)-[:KNOWS WHERE r.since > 2010]->(:Person)){1,3} (b:Person)
RETURN a.name, b.name
Bounded vs. unbounded:
- Always prefer bounded quantifiers ({1,5}) over unbounded (*, +)
- Unbounded traversals can cause exponential performance degradation
- Use LIMIT to constrain result sets
Label filtering:
// Good - filters early
MATCH (a:Person)-[:KNOWS*1..3]->(b:Person)
WHERE b.country = 'USA'
RETURN b.name
// Better - inline filtering
MATCH (a:Person)-[:KNOWS*1..3]->(b:Person WHERE b.country = 'USA')
RETURN b.name
Pattern Predicates¶
Pattern predicates are filtering expressions applied directly within patterns, evaluated during pattern matching rather than after.
Inline WHERE in Patterns¶
Node pattern predicates:
Relationship pattern predicates:
Quantified pattern predicates:
EXISTS Subqueries¶
Pattern existence check:
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)
WHERE movie.year >= 2000
AND EXISTS {
MATCH (actor)-[:ACTED_IN]->(other:Movie)
WHERE other.year < 2000
}
RETURN actor.name,
count(movie) AS recent_movies
ORDER BY recent_movies DESC
Negated existence - Nodes without certain patterns:
MATCH (person:Person)
WHERE NOT EXISTS {
MATCH (person)-[:ACTED_IN]->(:Movie)
}
RETURN person.name AS non_actor
Multiple existence checks:
MATCH (person:Person)
WHERE EXISTS {
MATCH (person)-[:ACTED_IN]->(:Movie)
}
AND EXISTS {
MATCH (person)-[:DIRECTED]->(:Movie)
}
RETURN person.name AS actor_director
COUNT Subqueries¶
Counting patterns:
MATCH (person:Person)
WHERE COUNT {
MATCH (person)-[:ACTED_IN]->(:Movie)
} > 10
RETURN person.name, COUNT {
MATCH (person)-[:ACTED_IN]->(:Movie)
} AS movie_count
ORDER BY movie_count DESC
Conditional filtering with counts:
MATCH (user:User)
WHERE COUNT {
MATCH (user)-[:PURCHASED]->(product:Product)
WHERE product.category = 'Electronics'
} >= 3
RETURN user.name, user.email
Pattern Predicate Use Cases¶
-
Complex filtering during traversal:
-
Existence-based filtering:
-
Relationship property constraints:
-
Multi-condition pattern existence:
Optional Patterns¶
Optional patterns use OPTIONAL MATCH to match patterns that may not exist, returning null for missing parts instead of eliminating rows.
Basic OPTIONAL MATCH¶
Simple optional relationship:
MATCH (person:Person)
OPTIONAL MATCH (person)-[:ACTED_IN]->(movie:Movie)
RETURN person.name,
movie.title,
CASE WHEN movie IS NULL THEN 'Not an actor' ELSE 'Actor' END AS status
Multiple optional patterns:
MATCH (person:Person {name: 'Tom Hanks'})
OPTIONAL MATCH (person)-[:ACTED_IN]->(movie:Movie)
OPTIONAL MATCH (person)-[:DIRECTED]->(directed:Movie)
RETURN person.name,
collect(DISTINCT movie.title) AS acted_in,
collect(DISTINCT directed.title) AS directed
Optional Patterns with WHERE¶
Filtering optional patterns:
MATCH (person:Person)
OPTIONAL MATCH (person)-[:ACTED_IN]->(movie:Movie)
WHERE movie.year > 2010
RETURN person.name,
count(movie) AS recent_movies
Note: WHERE predicates in OPTIONAL MATCH are evaluated during pattern matching, not after. This affects which rows contain null values.
Combining Required and Optional¶
Mixed pattern matching:
MATCH (user:User)
WHERE user.status = 'active'
OPTIONAL MATCH (user)-[:PURCHASED]->(product:Product)
OPTIONAL MATCH (user)-[review:REVIEWED]->(product)
RETURN user.name,
collect(DISTINCT product.name) AS purchased_products,
collect(DISTINCT review.rating) AS reviews
ORDER BY user.name
Nested optional patterns:
MATCH (company:Company)
OPTIONAL MATCH (company)<-[:WORKS_AT]-(employee:Employee)
OPTIONAL MATCH (employee)-[:MANAGES]->(report:Employee)
RETURN company.name,
count(DISTINCT employee) AS employee_count,
count(DISTINCT report) AS managed_count
Optional Pattern Use Cases¶
-
Outer join behavior:
-
Conditional data retrieval:
-
Checking relationship existence:
-
Exploring incomplete schemas:
Pattern Comprehension¶
Pattern comprehension provides list-based operations over pattern matches, similar to list comprehensions in Python.
Syntax¶
Basic Pattern Comprehension¶
Simple comprehension:
MATCH (person:Person)
RETURN person.name,
[(person)-[:ACTED_IN]->(movie:Movie) | movie.title] AS movies
With WHERE filtering:
MATCH (person:Person)
RETURN person.name,
[(person)-[:ACTED_IN]->(m:Movie) WHERE m.year > 2010 | m.title] AS recent_movies
Complex projections:
MATCH (actor:Person)
WHERE actor.name = 'Tom Hanks'
RETURN [(actor)-[:ACTED_IN]->(m:Movie) | {title: m.title, year: m.year, rating: m.rating}] AS filmography
Pattern Comprehension Use Cases¶
-
Collecting related data:
-
Filtered collection:
-
Aggregating across relationships:
-
Nested pattern comprehension:
Multiple Patterns¶
Multiple patterns can be combined in a single MATCH clause or across multiple MATCH clauses, with different semantics.
Multiple Patterns in Single MATCH¶
Comma-separated patterns - All must match for row to be returned:
MATCH (a:Person)-[:KNOWS]->(b:Person),
(b)-[:KNOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN a.name, b.name, c.name
Equivalent to chained pattern:
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN a.name, b.name, c.name
Independent patterns:
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie),
(director:Person)-[:DIRECTED]->(movie)
RETURN actor.name AS actor,
director.name AS director,
movie.title
Multiple MATCH Clauses¶
Sequential matching - Each MATCH builds on previous:
MATCH (person:Person {name: 'Alice'})
MATCH (person)-[:KNOWS]->(friend:Person)
MATCH (friend)-[:LIVES_IN]->(city:City)
RETURN friend.name, city.name
Cartesian product - Independent MATCH clauses:
MATCH (actor:Person)-[:ACTED_IN]->(:Movie)
MATCH (director:Person)-[:DIRECTED]->(:Movie)
RETURN DISTINCT actor.name, director.name
LIMIT 10
Multiple Pattern Use Cases¶
-
Finding mutual connections:
-
Complex relationships:
-
Independent pattern matching:
Pattern Matching Semantics¶
Understanding how patterns are evaluated is crucial for writing correct and efficient queries.
Uniqueness Constraints¶
Relationship uniqueness (default): Each relationship can appear at most once in a single pattern match:
// r1, r2, r3 are guaranteed to be different relationships
MATCH (a)-[r1]->(b)-[r2]->(c)-[r3]->(d)
RETURN count(*)
Node repetition allowed: The same node can appear multiple times:
// a and c can be the same node
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN a.name, b.name, c.name
Pattern Binding¶
Variable scope: Variables bound in patterns are available in subsequent clauses:
Pattern reuse: Variables from one pattern can be referenced in another:
MATCH (a:Person {name: 'Alice'})
MATCH (a)-[:KNOWS]->(friend:Person) // reuses 'a' from previous MATCH
RETURN friend.name
Null Handling¶
Property access on null:
MATCH (person:Person)
OPTIONAL MATCH (person)-[:LIVES_IN]->(city:City)
RETURN person.name,
city.name // null if city doesn't exist
Null in comparisons:
MATCH (person:Person)
OPTIONAL MATCH (person)-[:AGED]->(age)
WHERE age.value > 30 // null ages are excluded (ternary logic)
RETURN person.name
Pattern Evaluation Order¶
Left-to-right evaluation:
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
// Evaluated as: Find a, then find b connected to a, then find c connected to b
Optimization: The query planner may reorder evaluation for performance, but results are logically equivalent.
Pattern Performance Considerations¶
Writing efficient patterns is essential for query performance, especially on large graphs.
Indexing¶
Create indexes on frequently matched properties:
// Recommended for Person.name lookups
CREATE INDEX person_name FOR (p:Person) ON (p.name)
MATCH (p:Person {name: 'Alice'}) // Uses index
RETURN p
Composite indexes for multiple properties:
CREATE INDEX movie_year_rating FOR (m:Movie) ON (m.year, m.rating)
MATCH (m:Movie {year: 2020})
WHERE m.rating > 8.0 // Uses composite index
RETURN m.title
Pattern Anchoring¶
Anchor patterns with specific nodes:
// Good - starts with specific node
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*1..3]->(friend:Person)
RETURN friend.name
// Bad - starts with unbounded scan
MATCH (person:Person)-[:KNOWS*1..3]->(friend:Person)
WHERE person.name = 'Alice'
RETURN friend.name
Variable-Length Bounds¶
Always bound variable-length patterns:
// Good - bounded
MATCH (a)-[:KNOWS*1..5]->(b)
RETURN count(*)
// Dangerous - unbounded, can explode
MATCH (a)-[:KNOWS*]->(b)
RETURN count(*)
Early Filtering¶
Filter as early as possible:
// Good - inline filtering
MATCH (p:Person WHERE p.country = 'USA')-[:KNOWS]->(f:Person WHERE f.age > 30)
RETURN p.name, f.name
// Less efficient - filtering after matching
MATCH (p:Person)-[:KNOWS]->(f:Person)
WHERE p.country = 'USA' AND f.age > 30
RETURN p.name, f.name
LIMIT Usage¶
Use LIMIT to constrain result sets:
MATCH (person:Person)
RETURN person.name
ORDER BY person.name
LIMIT 100 // Prevents returning millions of rows
Pattern Specificity¶
Be as specific as possible:
// Good - specific labels and types
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)
RETURN count(*)
// Slower - generic patterns
MATCH (a)-[r]->(b)
WHERE 'Person' IN labels(a) AND 'Movie' IN labels(b) AND type(r) = 'ACTED_IN'
RETURN count(*)
Summary¶
OpenCypher pattern matching provides a rich, declarative syntax for querying graph data:
Pattern Types:
- Node patterns: (), (n), (:Label), (n:Label {prop: value})
- Relationship patterns: -[]->, <-[r:TYPE]-, -[r {prop: value}]-
- Path patterns: path = (a)-[]->(b)-[]->(c)
- Variable-length: -[*1..5]->, ((pattern)){1,3}, -[:TYPE]->{2,4}
- Optional: OPTIONAL MATCH (n)-[]->(m)
- Pattern comprehension: [(n)-[]->(m) WHERE condition | m.prop]
Key Principles: - Patterns are declarative - describe structure, not algorithm - Relationship uniqueness enforced by default - Use indexes for performance - Bound variable-length patterns - Filter early with inline predicates - Anchor patterns with specific starting nodes
Best Practices:
- Use specific labels and relationship types
- Create indexes on frequently queried properties
- Limit result sets with LIMIT
- Use OPTIONAL MATCH for outer join semantics
- Leverage pattern comprehension for complex projections
- Always bound variable-length patterns ({1,5} not *)
References¶
- OpenCypher Official Site: https://opencypher.org/
- OpenCypher Specification: https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf
- Neo4j Cypher Manual - Patterns: https://neo4j.com/docs/cypher-manual/current/patterns/
- Neo4j Cypher Manual - Clauses: https://neo4j.com/docs/cypher-manual/current/clauses/
Document Version: 1.0 Last Updated: 2026-02-16 OpenCypher Version: Based on OpenCypher 9 and Neo4j Cypher Manual (Current)