Using Neo4j Composite Indexes for Efficient Multi‑Property Filtering
Learn how to create a composite index that lets Neo4j satisfy equality filters on multiple properties with a single Index Seek, plus limits, verification steps, and rollback.
03 May 2026, 14:05 UTC

When to use a Neo4j composite index
If you repeatedly run a query that filters on two or more properties of the same label with equality predicates (and possibly a range on the last property), a single composite index can replace several single‑property indexes. The index lets Neo4j perform an Index Seek on the leading equality‑bound columns and then a range scan on the final column, reducing the amount of work compared to a NodeByLabelScan or multiple separate seeks.
Worked example: creating the index
Assume you have a :Person label with properties lastName, firstName, and birthYear. A typical lookup is:
MATCH (p:Person)
WHERE p.lastName = $ln
AND p.firstName = $fn
AND p.birthYear >= $y
RETURN p;
To support this pattern, create a composite index that lists the equality‑filtered properties first, ordered by expected selectivity (most selective first).
CREATE INDEX person_name_birth FOR (p:Person) ON (p.lastName, p.firstName, p.birthYear);
Run this command as a user with the INDEX_ADMIN role (or equivalent) on the Neo4j instance. Index creation is online but will consume I/O and memory while populating; schedule it during off‑peak hours for large datasets.
How the index is used
Neo4j consults the index in the order defined:
- It seeks to the first entry matching
p.lastName = $ln. - Within that subset, it seeks to
p.firstName = $fn. - Finally, it scans the range
p.birthYear >= $ybecause the leading columns are bound with equality.
The resulting plan shows an Index Seek followed by a Filter for the range predicate, avoiding a full label scan.
Limits and common mistakes
- Leading prefix required: If you omit the first property (
lastName) or use onlyfirstNameandbirthYear, the index cannot be used for a seek; Neo4j falls back to aNodeByLabelScanor separate seeks. - OR across properties: A predicate like
WHERE p.lastName = $ln OR p.firstName = $fnprevents the composite index from being used as a single seek. - Range on non‑leading column: Placing a range on
firstName(the second column) while leavinglastNameunbound forces a scan after the first equality, reducing selectivity. - Low‑cardinality leading column: Putting a property with few distinct values (e.g.,
status) first creates large index ranges, making the index less effective than a single‑property index on a more selective column. - Redundant indexes: Maintaining both a composite index and separate single‑property indexes on the same columns wastes write throughput and storage.
- ORDER BY expectations: The composite index only helps ordering if the
ORDER BYcolumns match a left‑most prefix of the index and are preceded by equality filters on any earlier columns.
Verification
After creating the index, confirm it is online and being used:
- List indexes:
CALL db.indexes() YIELD name, type, state, populationPercent RETURN *;Look forperson_name_birthwith stateONLINE. - Schema view: Run
:schemain Neo4j Browser; the index should appear under the:Personlabel. - Explain the query:
EXPLAIN MATCH (p:Person) WHERE p.lastName = $ln AND p.firstName = $fn AND p.birthYear >= $y RETURN p;Verify the plan contains anIndex Seeknode forperson_name_birthand not aNodeByLabelScan. - Profile for actual rows:
PROFILE MATCH (p:Person) WHERE p.lastName = $ln AND p.firstName = $fn AND p.birthYear >= $y RETURN p;Check that thedb hitsfor the index seek are low relative to the total node count. - Parameter test: Run the query with different values for
$lnand$fnwhile keeping the equality pattern; the plan should remain an index seek.
Rollback (dropping the index)
If the index proves unnecessary or causes excessive write overhead, remove it with:
DROP INDEX person_name_birth;
This operation also requires INDEX_ADMIN privileges and will briefly block writes while the index metadata is cleared. Ensure no active queries depend on the index before dropping.
Version and operational cautions
Composite index syntax shown (CREATE INDEX … ON (n.p1, n.p2)) is available in Neo4j 4.3 and later. Earlier versions used different procedures and had limited support for range scans on non‑leading columns. Index creation consumes temporary disk space and can increase write latency; monitor dbms.query.time and system.cpu metrics during the build. Composite indexes do not substitute for full‑text or vector indexes when you need text similarity or semantic search.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.