Creating Property Indexes

Introduction

Welcome back to Indexes and Performance! In this second lesson, we'll explore one of the most important performance-optimization techniques: creating and managing indexes. As your graph database grows, the ability to quickly locate specific nodes and relationships becomes critical. Without proper indexing, queries can become slow as Neo4j must scan through every node or relationship to find matches. Indexes provide a fast lookup mechanism that can dramatically improve query performance, often reducing execution time from seconds to milliseconds. In this lesson, we'll learn how to create indexes, view them, measure their impact, and understand when they're beneficial.

Why Indexes Matter in Graph Databases

Indexes in graph databases work similarly to indexes in traditional databases: they create an optimized data structure that allows the database to quickly find nodes or relationships based on property values. Imagine searching for a specific person in a social network with millions of users. Without an index, Neo4j would need to examine every single User node to find the one you're looking for. With an index on the name property, Neo4j can jump directly to the relevant nodes in just a few operations. This becomes especially important in graph queries where we typically start by finding specific anchor nodes and then traverse relationships from there. The faster we can locate those starting points, the faster our entire query executes.

Understanding Index Mechanics

When you create an index in Neo4j, the database builds a separate data structure that maps property values to the nodes or relationships that contain them. This structure is maintained automatically as you add, update, or delete data. Neo4j uses range indexes by default, which are optimized for equality checks, range queries, and sorting operations. These indexes support common query patterns like finding exact matches (WHERE u.name = 'Alice'), range conditions (WHERE u.age > 25), and prefix matching (WHERE u.name STARTS WITH 'Al'). The query planner automatically decides whether to use an available index based on the query structure and estimated performance benefits.

Creating Your First Index

Let's create our first index on the name property of User nodes. This will speed up any queries that filter or search by user names:

// Index on name property
CREATE INDEX user_name FOR (u:User) ON (u.name)

This statement creates a range index named user_name that applies to all nodes with the User label. The syntax follows this pattern:

  • CREATE INDEX followed by the index name
  • FOR (u:User) specifies the node label pattern
  • ON (u.name) indicates which property to index

Once created, Neo4j will populate this index with all existing User nodes and automatically maintain it as data changes.

Adding Another Index

Email addresses are another common property used in queries, especially for user lookups and authentication flows. Let's create an index for the email property:

// Index on email
CREATE INDEX user_email FOR (u:User) ON (u.email)

This follows the same pattern as our name index but targets the email property. In practice, if email serves as a unique identifier for users, you might consider using a uniqueness constraint instead, which automatically creates an index while also enforcing data integrity. We'll explore uniqueness constraints in detail in a later lesson. However, for learning purposes, a standard index demonstrates the core concepts clearly.

Viewing All Indexes

After creating indexes, we need a way to verify they exist and check their status. Neo4j provides the SHOW INDEXES command for this purpose:

// View all indexes
SHOW INDEXES

Output:

idnamestatepopulationPercenttypeentityTypelabelsOrTypespropertiesindexProviderowningConstraintlastReadreadCount
2index_1b9dcc97ONLINE100.0LOOKUPRELATIONSHIPNULLNULLtoken-lookup-1.0NULLNULL0
1index_460996c0ONLINE100.0LOOKUPNODENULLNULLtoken-lookup-1.0NULLNULL0
4user_emailONLINE100.0RANGENODE["User"]["email"]range-1.0NULLNULLNULL
3user_nameONLINE100.0RANGENODE["User"]["name"]range-1.0NULLNULLNULL

Notice that the output includes not just our user_name and user_email indexes, but also two LOOKUP indexes. These are system indexes that Neo4j creates automatically to optimize label and relationship type lookups. You'll always see these in your database.

This command returns a table with comprehensive information about all indexes in the database:

  • name: the index name we specified (or auto-generated for system indexes)
  • type: the kind of index (RANGE, LOOKUP, TEXT, FULLTEXT, etc.)
  • state: whether the index is ONLINE, POPULATING, or FAILED
  • populationPercent: the completion percentage for newly created indexes
  • entityType: whether this indexes nodes or relationships
  • labelsOrTypes: which labels or relationship types the index covers
  • properties: which properties are indexed
  • indexProvider: the underlying index implementation used
  • owningConstraint: the name of the constraint that created this index (NULL if created independently)
  • lastRead: timestamp of when this index was last used by a query (NULL if never used)
  • readCount: the total number of times this index has been accessed by queries

When Indexes Help Performance

Indexes provide the greatest benefit when they help Neo4j narrow down large datasets to small, relevant subsets. They're most effective for properties with high selectivity, meaning many distinct values. For example, indexing user IDs, email addresses, or product SKUs makes sense because each value typically identifies a small number of nodes. Indexes also excel in graph traversal patterns where we anchor the query by finding specific starting nodes first, then exploring relationships from there. A typical pattern is matching users by name or ID, then traversing their social connections or purchase history.

Measuring Index Impact with PROFILE

To verify that an index actually improves performance, we use the PROFILE command. Let's compare the same query before and after creating an index:

// Without index
PROFILE
MATCH (u:User)
WHERE u.name = 'Alice'
RETURN u

Output (Without Index):

Time: 36
DbHits: 1053
Rows: 1

Now with the index:

// Create the index
CREATE INDEX user_name FOR (u:User) ON (u.name);

// With index
PROFILE
MATCH (u:User)
WHERE u.name = 'Alice'
RETURN u

Output (With Index):

Time: 2
DbHits: 4
Rows: 1

The execution time dropped from 36 to 2ms — an 18x performance improvement! Notice also that database hits decreased dramatically from 1,053 to just 4. Without the index, Neo4j had to scan through all User nodes examining each one's name property. With the index, it jumped directly to the matching node using the index lookup mechanism. The dramatic reduction in both execution time and database hits clearly demonstrates the value of proper indexing.

Understanding Index Overhead

While indexes speed up reads, they come with tradeoffs that we must consider. Every time you create, update, or delete a node with indexed properties, Neo4j must update the index structures as well. This creates write amplification: a single property change triggers multiple storage operations. Indexes also consume disk space and compete with graph data for memory cache. For write-heavy workloads or properties with low selectivity (like boolean flags), the overhead may outweigh the benefits. The key is finding balance: index properties that appear frequently in query filters and have high cardinality, but avoid over-indexing properties that are rarely queried or have few distinct values.

Conclusion and Next Steps

In this lesson, we've covered the fundamentals of creating indexes in Neo4j to optimize query performance. We learned how to create indexes on node properties using the CREATE INDEX syntax, view all existing indexes with SHOW INDEXES, and understand when indexes provide genuine performance benefits versus when they introduce unnecessary overhead. We also explored how to measure index impact using the PROFILE command to confirm the query planner is utilizing our indexes effectively. The concepts of index selectivity, write amplification, and proper index design will serve as guiding principles as you optimize your own graph databases. Now it's time to put these concepts into practice and create indexes that will make your queries fly!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal