Neo4j Certified Professional study materials
Neo4j Certified Professional study resources

Neo4j Certified Professional common mistakes

Neo4j Certified Professional · Understand the traps and how to avoid them.

Mistake 1

Writing `MATCH (n) WHERE n.name = 'Alice'` and then trying to filter on a relationship property in the same WHERE clause, e.g. `WHERE r.since > 2020`, when `r` was never bound in the pattern.

Candidates carry over SQL habits where a joined table is implicitly available; in Cypher every variable used in WHERE must be introduced by the MATCH, OPTIONAL MATCH, WITH, or a pattern comprehension. The engine raises a Variable not defined error, and under time pressure candidates guess at fixes instead of reading the error.

What to do instead

Bind the relationship in the pattern itself: MATCH (n {name:'Alice'})-[r:KNOWS]->() WHERE r.since > 2020 RETURN n. Before running, scan the WHERE clause and confirm each identifier appears on the left side of the preceding clause. Practise reading the exact error text so you can locate the unbound variable immediately.

Mistake 2

Using `MATCH` where `OPTIONAL MATCH` is required, so nodes with no matching relationship silently disappear from the result set.

Candidates test on data where every node has the relationship, so the result looks correct; the outer-join semantics of OPTIONAL MATCH are the Cypher feature most often confused with SQL LEFT JOIN, and exam questions deliberately include a node without the relationship.

What to do instead

Ask whether every row of the left side must survive. If yes, use OPTIONAL MATCH and handle the null with coalesce() or WHERE r IS NULL. Verify by running the query against a deliberately isolated node and confirming it still appears.

Mistake 3

Confusing `DELETE` with `DETACH DELETE`, or attempting `DELETE n` on a node that still has relationships and treating the resulting error as a syntax problem.

Candidates learn DELETE first and reuse it; Neo4j refuses to delete a node with relationships unless they are detached, and the error message is about the constraint rather than the syntax, which misleads candidates.

What to do instead

Remember: DELETE removes nodes and relationships but fails if relationships remain; DETACH DELETE removes the node and all its relationships. In a write query, delete relationships explicitly first if you need control, or use DETACH DELETE when the whole subgraph should go. Also remember DELETE is for deletion only; use REMOVE for properties and labels.

Mistake 4

Assuming a uniqueness constraint or an index is enforced retroactively, or expecting a constraint to be created on data that already violates it.

Candidates treat constraints as declarative guarantees that will simply apply to existing data. In practice, creating a uniqueness constraint fails if duplicate values already exist, and candidates then misdiagnose the failure as a syntax error in the constraint statement.

What to do instead

Before creating a constraint, run an aggregation to find violations, e.g. MATCH (n:Person) WITH n.email AS e, count(*) AS c WHERE c > 1 RETURN e, c, and clean the data first. Know that constraints are enforced on future writes and checked at creation time against existing data. Also distinguish node property uniqueness from relationship property uniqueness and from existence/type constraints.

Mistake 5

Assuming an index will be used for any property lookup, including `CONTAINS`, `ENDS WITH`, and regular-expression predicates on a standard index.

Candidates generalise from relational B-tree indexes. Neo4j's default index supports equality and range comparisons, not arbitrary substring matching; full-text and text indexes exist for that. Candidates then conclude indexes are broken when a CONTAINS query still scans.

What to do instead

Match the index type to the predicate: range index for equality, range, and prefix-style comparisons; full-text index for tokenised text search; text index for substring and suffix matching. Confirm with EXPLAIN that the plan shows the index operator rather than an AllNodesScan or NodeByLabelScan.

Mistake 6

Modelling relationships as nodes by default, turning every many-to-many association into an intermediate entity node even when it carries no properties and no further relationships.

Relational training teaches that many-to-many needs a junction table, so candidates translate that mechanically into a node. This inflates traversal length, complicates every query, and is a classic modelling error the exam probes.

What to do instead

Use a relationship when the association is between exactly two entities and needs no further connections; promote it to a node only when it must connect to more than two things or needs to be the anchor of its own graph of facts. State the rule explicitly when justifying a model: relationship for binary, property-carrying associations; node for n-ary or connection-bearing ones.

Mistake 7

Writing a driver session that opens a new driver per request, or forgetting to close sessions and the driver, and assuming the driver is cheap to construct.

Candidates model the driver on a database connection and instantiate it inside a request handler. The driver holds a connection pool and is designed to be a long-lived singleton; per-request construction exhausts connections and adds handshake latency.

What to do instead

Create one driver per application, reuse it across requests, open a short-lived session per unit of work, and close sessions (try-with-resources or equivalent). Close the driver only at application shutdown. Know the difference between session and transaction scope, and that executeRead/executeWrite add retry for transient failures.

Mistake 8

Concatenating user input into a Cypher string instead of using parameters, then being surprised that a value containing a quote breaks the query or that the plan is not cached.

String building is the path of least resistance and works for simple values, so candidates never see the failure until a value contains a quote or a list is passed. Parameters also enable plan caching, which candidates often do not connect to query construction.

What to do instead

Always pass values as parameters: session.run('MATCH (n:Person {name: $name}) RETURN n', {name: name}). Parameters cannot be used for labels, relationship types, or property names, so validate those against an allow-list and interpolate only after validation. This is also the primary defence against Cypher injection.

Mistake 9

Using `LOAD CSV` without `USING PERIODIC COMMIT` for a large file, and creating nodes with `CREATE` inside a loop that re-imports duplicates on every re-run.

Candidates test on a handful of rows where memory is irrelevant and idempotency never matters. On a real file the transaction state grows until it fails, and re-running the script doubles the data, which then breaks uniqueness constraints added later.

What to do instead

For large imports use USING PERIODIC COMMIT (or batched CALL {} IN TRANSACTIONS in newer versions) to bound transaction size. Make imports idempotent with MERGE on a stable key rather than CREATE, and create the uniqueness constraint before importing so MERGE is fast and duplicates are impossible. Also handle the header row and trim whitespace.

Mistake 10

Treating `MERGE` as an upsert that matches on the whole pattern, then adding `SET` on properties that were part of the match criteria, so the query creates a new node instead of updating the existing one.

Candidates write MERGE (n:Person {name:'Alice', age:30}) and later change the age, expecting an update; because age is inside the MERGE pattern, no match is found and a second node is created. The semantics of MERGE as match-or-create-entire-pattern are subtle and frequently tested.

What to do instead

MERGE only on the identity keys, then set the mutable properties afterwards: MERGE (n:Person {id: $id}) SET n.name = $name, n.age = $age. Use ON CREATE SET and ON MATCH SET to branch behaviour. Remember MERGE needs a uniqueness constraint on the merge key to be efficient and to avoid race-created duplicates.