Most graph database tutorials start with toy examples, three nodes, two edges, a single MATCH clause. Then you hit production, where queries span dozens of relationship types, filter across compound properties, and need to return results in single-digit milliseconds. The gap between tutorial Cypher and production Cypher is where most developers lose time.
This cypher query cheatsheet for developers bridges that gap. It covers the patterns, syntax, and performance techniques you actually need when building against a graph database, from basic node matching through variable-length traversals, mutation operations, and indexing strategies.
The examples here target Cypher, the declarative graph query language originally developed for Neo4j and now implemented across multiple engines, including FalkorDB. Where FalkorDB extends or diverges from standard Cypher, those differences are called out explicitly.
Basic Node and Relationship Patterns
Every Cypher query starts with a pattern. Patterns describe the shape of the subgraph you want to match, using ASCII-art syntax that maps directly to nodes (parentheses) and relationships (square brackets on arrows).
Node Patterns
Nodes are wrapped in parentheses. A label filters the node type; properties filter further.
(n): Any node, bound to variablen(n:User): Node with the labelUser(n:User {status: 'active'}): Node with label and inline property filter(): Anonymous node, matched but not bound to a variable
Relationship Patterns
Relationships connect nodes with directional arrows. The relationship type sits inside square brackets.
(a)-[r:FOLLOWS]->(b): Directed relationship of typeFOLLOWS(a)-[r]-(b): Any relationship, any direction(a)-[:PURCHASED]->(p:Product): Typed relationship to a labeled node
A simple full query combines these into a readable statement. MATCH (u:User)-[:FOLLOWS]->(f:User) RETURN u.name, f.name returns every follow pair in the graph. If you are building your first queries against a graph engine, the complete Cypher language guide walks through the full syntax tree in detail.
Filtering, Aggregation, and Sorting in Your Cypher Query Cheatsheet
Raw pattern matches return every instance in the graph. Production queries need WHERE clauses, aggregation functions, and ordering to return useful result sets.
WHERE Clauses
WHERE accepts boolean expressions, comparisons, string matching, and list predicates. Key patterns:
- Comparison :
WHERE n.age > 25 AND n.age < 65 - String matching :
WHERE n.name STARTS WITH 'Al', also supportsENDS WITHandCONTAINS - List membership :
WHERE n.role IN ['admin', 'editor'] - Existence check :
WHERE n.email IS NOT NULL - Pattern predicate :
WHERE (n)-[:MANAGES]->(), filters nodes that participate in a specific pattern
Aggregation Functions
Cypher aggregates implicitly, non-aggregated columns become the grouping key. No GROUP BY clause needed.
count(n): Number of matched nodessum(n.amount): Numeric sumavg(n.score): Average valuecollect(n.name): Aggregates values into a listmin(n.created_at),max(n.created_at): Boundary values
Example: MATCH (u:User)-[:PURCHASED]->(p:Product) RETURN u.name, count(p) AS purchases ORDER BY purchases DESC LIMIT 10 returns the top 10 buyers. ORDER BY sorts results; LIMIT and SKIP handle pagination.
Understanding how Cypher compares to SQL for aggregation and joins clarifies why the implicit grouping model reduces boilerplate in relationship-heavy queries.

Path Traversal and Variable-Length Queries
Fixed-length patterns cover direct connections. Real graph problems, shortest paths, reachability analysis, influence chains, require variable-length traversals.
Variable-Length Relationships
The syntax *min..max inside a relationship bracket defines the hop range.
(a)-[:FOLLOWS*1..3]->(b): One to three hops alongFOLLOWSedges(a)-[:FOLLOWS*..5]->(b): Up to five hops (minimum defaults to 1)(a)-[:FOLLOWS*]->(b): Unbounded depth, use with caution on large graphs
Unbounded traversals can explode in runtime. Always set an upper bound unless you have a known-small subgraph. For production workloads, capping traversal depth at a reasonable maximum (typically 4-7 hops) prevents query timeouts while covering most real-world relationship chains.
Shortest Path
Cypher provides built-in shortest path functions. shortestPath((a)-[*..10]->(b)) returns a single shortest path between two nodes. allShortestPaths((a)-[*..10]->(b)) returns all paths tied at the minimum length.
Bind the result to a variable to inspect it: MATCH p = shortestPath((a:User {id: 1})-[*..6]->(b:User {id: 99})) RETURN nodes(p), relationships(p), length(p). The functions nodes(), relationships(), and length() extract components from the path. If you are new to how graph databases handle traversals, understanding the underlying adjacency-list storage model explains why these operations outperform relational JOINs.
Graph Mutation: CREATE, MERGE, DELETE
Reading data is half the job. Production applications also need to write, update, and delete graph elements, with idempotency guarantees where possible.
CREATE
CREATE inserts nodes and relationships unconditionally. It does not check for duplicates.
- Create a node :
CREATE (n:User {name: 'Alice', age: 32}) - Create a relationship :
MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'}) CREATE (a)-[:FOLLOWS {since: 2024}]->(b) - Create a path :
CREATE (a:User {name: 'Carol'})-[:WORKS_AT]->(c:Company {name: 'Acme'})
MERGE
MERGE is Cypher’s upsert. It matches an existing element or creates it if absent, critical for preventing duplicate nodes during data ingestion.
- Merge a node :
MERGE (n:User {email: 'alice@example.com'}) ON CREATE SET n.created = timestamp() ON MATCH SET n.lastSeen = timestamp() - Merge a relationship :
MATCH (a:User {id: 1}), (b:User {id: 2}) MERGE (a)-[:FOLLOWS]->(b)
ON CREATE SET and ON MATCH SET let you differentiate behavior between the insert and update paths. Always merge on a unique key, merging on non-unique properties can still produce duplicates.
DELETE and DETACH DELETE
DELETE removes a node only if it has no relationships. DETACH DELETE removes the node and all connected relationships in a single operation.
- Delete a specific node :
MATCH (n:User {email: 'old@example.com'}) DETACH DELETE n - Remove a relationship :
MATCH (a)-[r:FOLLOWS]->(b) WHERE a.id = 1 AND b.id = 2 DELETE r - Update properties :
MATCH (n:User {id: 1}) SET n.status = 'inactive' - Remove a property :
MATCH (n:User {id: 1}) REMOVE n.status
Performance Tips: Indexes and Query Profiling
Correct syntax means nothing if queries scan every node in the graph. Indexing and profiling are the difference between sub-millisecond responses and multi-second scans.
Index Creation
Create indexes on properties used in WHERE clauses and MERGE keys. Without an index, the engine performs a full label scan.
- Exact-match index :
CREATE INDEX FOR (n:User) ON (n.email) - Composite index :
CREATE INDEX FOR (n:User) ON (n.lastName, n.firstName) - Relationship index : Some engines support indexing relationship properties, check your engine’s documentation
FalkorDB supports both exact-match and range indexing. For workloads involving array properties or numeric ranges, array and range indexing can reduce lookup complexity from linear to logarithmic time.
Query Profiling
Prefix any query with PROFILE or EXPLAIN to inspect the execution plan.
EXPLAIN: Shows the planned operations without executing, useful for checking index usagePROFILE: Executes the query and reports actual row counts and database hits per operation
Look for NodeByLabelScan in your profile output, that signals a missing index. Replace it with NodeIndexSeek by adding the appropriate index. On large graphs, a single missing index can degrade query time by three or more orders of magnitude.
| Plan Operator | Meaning | Action |
|---|---|---|
| NodeByLabelScan | Scanning all nodes with a label | Add an index on the filtered property |
| NodeIndexSeek | Using an index for lookup | No action, this is optimal |
| Expand(All) | Traversing all relationships from matched nodes | Consider adding relationship type filters |
| Filter | Post-scan filtering | Move filter predicate to an indexed property if possible |
FalkorDB-Specific Cypher Extensions: A Developer’s Cheatsheet Addition
FalkorDB implements the openCypher specification with several engine-specific extensions that affect performance and capability. You can find the full reference in the FalkorDB Cypher documentation.
- GRAPH.QUERY command : FalkorDB runs as a Redis module, so Cypher queries are dispatched via
GRAPH.QUERY graphname "MATCH ..."rather than a direct protocol endpoint - Multi-tenancy : Each graph is a separate key in Redis, enabling per-tenant graph isolation without additional infrastructure
- Timeout control : Queries accept a
timeoutparameter in milliseconds, preventing runaway traversals from blocking the server - User-defined functions : FalkorDB supports JavaScript-based UDFs for custom graph logic that executes inside the engine, useful for domain-specific scoring or transformation functions
FalkorDB’s in-memory execution model means write operations like CREATE and MERGE commit immediately without the WAL overhead typical of disk-first engines. For teams evaluating graph database architectures, this model trades durability configuration for throughput, persistence is handled through Redis-native RDB/AOF mechanisms.
The Securin team demonstrated this performance advantage in production, running 7-hop threat intelligence queries in under 350 milliseconds across large-scale security graphs.
Frequently Asked Questions
What is the difference between CREATE and MERGE in Cypher?
CREATE always inserts a new element, while MERGE checks for an existing match first and only creates if none is found.
- Use
MERGEduring data ingestion to prevent duplicate nodes, always merge on a unique identifier property ON CREATE SETandON MATCH SETlet you run different logic depending on whether the node was inserted or already existedCREATEis faster for bulk loading when you guarantee uniqueness upstream- Both operations work on nodes and relationships identically in terms of syntax
How do I optimize slow Cypher queries?
Start by profiling the query with PROFILE or EXPLAIN to identify full label scans and unindexed property filters.
- Add indexes on every property used in
WHEREclauses andMERGEmatch keys - Bound variable-length traversals with an explicit upper limit to prevent exponential path expansion
- Use specific relationship types in patterns instead of matching all relationships
- Check the range indexing guide for advanced index types that accelerate numeric and array lookups
Can Cypher handle multi-hop traversals efficiently?
Yes, Cypher supports variable-length patterns that traverse multiple hops, and graph engines are architecturally optimized for these operations.
- Syntax:
(a)-[:REL*1..5]->(b)matches paths between one and five hops shortestPath()computes minimum-hop routes without exploring the full graph- Always set an upper bound on unbounded traversals in production to avoid timeouts
- Multi-hop efficiency is a core advantage of graph database architecture over relational JOIN chains
What are FalkorDB’s main differences from Neo4j’s Cypher?
FalkorDB implements openCypher with extensions specific to its Redis-based, in-memory architecture.
- Queries are dispatched via
GRAPH.QUERYas a Redis command rather than a Bolt protocol connection - Each graph is a separate Redis key, enabling straightforward multi-tenancy
- FalkorDB supports JavaScript UDFs for custom graph extension logic inside the engine
- Persistence uses Redis RDB/AOF rather than a custom transaction log
How should I structure indexes in a graph database?
Index every property that appears in a WHERE filter, a MERGE match key, or an ORDER BY clause for any query that runs frequently.
- Start with single-property indexes on high-cardinality fields like email, ID, or username
- Add composite indexes when queries consistently filter on two properties together
- Monitor query plans with
PROFILE, look forNodeByLabelScanas the signal that an index is missing - Review Cypher language fundamentals to understand how the query planner selects indexes
Is this cypher query cheatsheet applicable to all graph databases?
The core syntax covered here follows the openCypher specification, which is supported by multiple graph engines including Neo4j, FalkorDB, and Memgraph.
- Node/relationship patterns,
WHERE,CREATE,MERGE, and aggregation functions are portable across openCypher-compliant engines - Performance features like index syntax and profiling output vary by engine
- FalkorDB-specific extensions (e.g.,
GRAPH.QUERY, timeout parameters) only apply to FalkorDB deployments
Building Production-Ready Cypher Queries
This cypher query cheatsheet for developers covers the patterns that appear in nearly every production graph application, from basic node matching through mutation, traversal, and performance tuning. The syntax is intentionally concise because Cypher’s power lies in its readability: a well-written query describes the data shape you want, and the engine figures out how to retrieve it.
The practical next step is to run these patterns against real data. Spin up a FalkorDB instance, load a dataset that mirrors your domain, and profile every query before it reaches production. Index the properties you filter on, bound your traversals, and use MERGE over CREATE when idempotency matters.
Graph queries get complex fast, but the building blocks stay the same. Master the patterns in this cheatsheet, and you have the foundation for any graph-powered application.