Cypher#

Cypher is the query language for graph databases. Created at Neo4j; later open-sourced as openCypher and adopted by other graph engines (Memgraph, Amazon Neptune via openCypher, RedisGraph). The “SQL of the graph world”.

The defining feature: queries look like the data they’re querying. ASCII art of nodes and edges drives the syntax.

The ASCII-Art Graph#

In Cypher, nodes are (parens) and edges are -[brackets]->:

(alice)-[:FRIEND_OF]->(bob)

A pattern that says: match anywhere there’s a FRIEND_OF edge from a node we’ll call alice to a node we’ll call bob.

Reading the graph as a sentence is half the win.

The Basics#

The everyday Cypher operations cover finding by property, walking a relationship, traversing N hops, and finding the shortest path between two nodes. Each one packs a query that would take a join-heavy SQL stanza into a single readable line of pattern syntax.

// Find a person named Ada
MATCH (p:Person {name: 'Ada'})
RETURN p

// Friends of Ada
MATCH (a:Person {name: 'Ada'})-[:FRIEND_OF]->(f)
RETURN f.name

// Friends of friends, distinct
MATCH (a:Person {name: 'Ada'})-[:FRIEND_OF*2]->(fof)
WHERE fof <> a
RETURN DISTINCT fof.name

// Shortest path
MATCH p = shortestPath(
  (a:Person {name: 'Ada'})-[*..6]-(b:Person {name: 'Lin'})
)
RETURN p

Clauses#

The verbs of Cypher. MATCH reads, CREATE / MERGE writes, WITH chains stages, RETURN projects. Most of the table will look familiar to a SQL author – ORDER BY, LIMIT, UNION, CALL, with graph-shaped semantics underneath.

Clause

Purpose

MATCH

pattern match against the graph

OPTIONAL MATCH like SQL’s LEFT JOIN, match if present

WHERE

filter

RETURN

project result

WITH

chain query stages (like a CTE)

UNWIND

turn a list into rows

CREATE

create nodes / relationships

MERGE

match-or-create (upsert)

SET

update properties

DELETE /

DETACH DELETE remove nodes / relationships

ORDER BY /

LIMIT /

SKIP

SQL-style result shaping

UNION

combine results

CALL

invoke procedures

Pattern Syntax#

The patterns are the language. Parentheses describe nodes, square brackets describe edges, dashes connect them, arrows add direction. Labels and properties narrow the matches; star notation expresses variable-length traversal (two to five hops, any number, exactly four).

Pattern

Matches

(n)

any node, bind to n

(:Person)

node with label Person

(p:Person {name: 'Ada'}) node with label and property

(a)-->(b)

directed edge a → b

(a)<--(b)

directed edge a ← b

(a)--(b)

either direction

(a)-[r]->(b)

named edge r

(a)-[:KNOWS]->(b)

edge type KNOWS

(a)-[:KNOWS*2..5]->(b)

2 to 5 hops

(a)-[:KNOWS*]->(b)

any number of hops

Building Up Queries with WITH#

WITH is Cypher’s CTE: it pipes results from one stage to the next.

MATCH (a:Person)-[:FRIEND_OF]->(b)
WITH a, count(b) AS friend_count
WHERE friend_count > 5
RETURN a.name, friend_count
ORDER BY friend_count DESC
LIMIT 10

Writing Data#

Cypher’s mutating verbs. CREATE adds; MERGE upserts with optional ON CREATE / ON MATCH branches; SET updates properties or adds labels; DETACH DELETE removes a node and all its edges in one shot. The patterns mirror the read syntax so writes look like a planned subgraph edit.

// Create
CREATE (p:Person {name: 'Ada', age: 36})
CREATE (a)-[:FRIEND_OF {since: 2020}]->(b)

// Upsert
MERGE (p:Person {email: 'operator@example.com'})
ON CREATE SET p.created_at = timestamp()
ON MATCH  SET p.last_seen  = timestamp()

// Update
MATCH (p:Person {name: 'Ada'})
SET p.age = 37, p:Active

// Remove
MATCH (p:Person {name: 'Ada'})
DETACH DELETE p          // delete the node and all its edges

Aggregations#

MATCH (p:Person)-[:WROTE]->(post:Post)
RETURN p.name, count(post) AS posts, avg(post.length) AS avg_len

Where Cypher Wins#

Cypher is dramatically clearer than SQL for queries with deep traversal:

  • “Find everyone within 4 hops of Ada whose company is in France” – one Cypher pattern; a join-heavy nightmare in SQL.

  • “Detect cycles among accounts”, Cypher’s variable-length paths handle it naturally.

  • “Recommend products bought by people who bought what I bought” – graph projection with two MATCH clauses.

For tabular relational queries, SQL is still the right tool. Cypher shines exactly where SQL struggles.

Implementations#

The graph databases that speak Cypher. Neo4j is the reference; Memgraph is the in-memory openCypher rebuild; Amazon Neptune supports openCypher alongside Gremlin and SPARQL; Apache AGE bolts Cypher onto Postgres. The dialect is portable enough that queries move between engines.

  • Neo4j, the original; the reference Cypher.

  • Memgraph, in-memory, openCypher-compatible, C++.

  • Amazon Neptune, supports openCypher (alongside Gremlin and SPARQL).

  • RedisGraph, now EOL but spread Cypher into the Redis ecosystem.

  • Apache AGE, adds graph queries (openCypher) on top of PostgreSQL.

Other Graph Query Languages#

Cypher isn’t the only way to query a graph. Gremlin is the imperative traversal alternative; SPARQL queries RDF triples; GQL is the ISO-standardized successor. Picking openCypher in 2026 is the safest syntactic choice, and GQL ratifies it as the going-forward standard.

  • Gremlin, imperative graph traversal language; part of Apache TinkerPop. Different mental model: chained traversal steps rather than pattern matching. Strong in JanusGraph and Neptune.

  • SPARQL, query language for RDF / linked data. Pattern-based like Cypher but for triples (subject-predicate-object).

  • GQL (Graph Query Language), ISO standard adopted in 2024, built on Cypher’s foundations; long-term unification target.

If you’re picking a graph stack today, openCypher is the safest syntactic bet, and GQL ratifies it as the standard going forward.

Pitfalls#

The traps that catch teams new to Cypher. Missing indexes turn fast queries slow; disjoint MATCH patterns produce Cartesian explosions; uncapped variable-length paths exhaust memory; the schema-less default lets bad data accumulate. Each is a known issue with a one-line preventive fix.

  • Performance is sensitive to label and index choices. Always index the property you MATCH on with CREATE INDEX.

  • Cartesian products, MATCH (a), (b) without a relationship produces every pair. Avoid disjoint patterns; use WITH to chain.

  • Variable-length paths without an upper bound ([*]) can explode. Always cap them: [*..6].

  • Schema-less, Neo4j enforces only what you tell it via constraints; bad data goes in and silently breaks queries later. Define CREATE CONSTRAINT for keys and existence.

When (Not) to Use Cypher#

  • Use for relationship-heavy queries: social graphs, fraud detection, identity / access analyses, recommendations, dependency / impact analysis.

  • Don’t use for primary OLTP storage of tabular records, a relational database is usually a better fit.

  • Don’t use purely for “joins are slow” reasons, a well-indexed Postgres on millions of rows is often faster than a graph database.