What is a Database Index?

Last updated: July 2026

A database index is a sorted lookup structure that lets the engine find matching rows directly instead of scanning the whole table. The book analogy is exact: nobody finds “idempotency” in a 900-page book by reading it — they check the index and jump to the page. Databases make the same choice on every query, and whether they can jump is the single most common difference between a 5-millisecond query and a 5-second one.

Key takeaways

QuestionAnswer
What it isA sorted side-structure: values + pointers, like a book’s index
The payoffLogarithmic seek instead of linear scan — decisive at scale
The taxEvery write updates every index; storage grows
What to indexFilter, join/pointer, and sort columns — if selective
The diagnosticEXPLAIN: a full scan on a big table is the tell

The index, created and felt

-- The workhorse: a composite index shaped like the hot query
CREATE INDEX orders_pending ON orders (status, created_at);

-- Specializations of the same idea:
CREATE UNIQUE INDEX users_email ON users (email);         -- constraint + speed
CREATE INDEX unshipped ON orders (created_at)
  WHERE shipped = false;                                  -- partial: hot subset only

-- The before/after, via the plan:
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > now() - interval '1 day';
--  before:  Seq Scan on orders   (rows examined: 4,812,309)
--  after:   Index Scan using orders_pending   (rows examined: 1,214)

Application code meets the same physics through query shape — this query is the index specification (status, createdAt), whatever surface writes it:

// JavaScript / Node.js — Back4app JS SDK
// The query shape tells you the index: (status, createdAt)
const query = new Parse.Query('Order');
query.equalTo('status', 'pending');       // equality first…
query.greaterThan('createdAt', since);    // …then the range
query.descending('createdAt');            // …sorted by the same column
const backlog = await query.find();
// Indexed: milliseconds at any size. Unindexed: a full collection scan.

How the jump works

A B-tree index lookupA query descends a shallow sorted tree from the root through a branch node to a leaf holding the matching value and row pointers, touching a handful of pages instead of scanning the entire table.

Root node
(ranges)

Branch
A–M

Branch
N–Z

Leaf: 'pending' → rows 88, 1042, 55913

Leaf …

Fetch exactly those rows

A query descends a shallow sorted tree from the root through a branch node to a leaf holding the matching value and row pointers, touching a handful of pages instead of scanning the entire table.

The default structure everywhere is the B-tree: shallow, sorted, self-balancing — three or four levels cover hundreds of millions of entries, and its leaves are linked, which is why one structure serves equality, ranges, and ORDER BY alike. That versatility is why “when in doubt, B-tree” is the standing advice, with the alternatives as specialists.

B-tree vs. hash vs. the specialists

Which index type should you use?

TypeSupportsReach for it when
B-tree (default)=, <, >, ranges, sorts, prefixesAlmost always — the generalist
HashEquality only, O(1)Exact-match lookups, nothing else
CompositeMulti-column, leftmost-prefixThe hot query filters on several columns
UniqueB-tree + no duplicatesConstraint and index in one
PartialA filtered slice of rowsHot subsets: unshipped, unread, active
CoveringQuery answered from index aloneRead-heavy queries with a stable column list
Full-text (inverted)Words → documentsSearch boxes
GeospatialPoints, regions, distance”Near me” queries

Two rules carry the composite row: the leftmost-prefix rule — an index on (a, b, c) serves a, (a, b), (a, b, c), never b alone — and equality first, range/sort last in column order (the ESR mnemonic in document-database indexing, where the same B-trees do the same work).

When to index — and when not to

Index: columns in frequent WHERE filters; join keys and pointer fields (some engines never index foreign keys automatically — a classic silent scan); ORDER BY columns on hot paths; and always with selectivity in mind — an email column (millions of distinct values) earns its keep, a status flag (three values) usually doesn’t alone, though it shines leading a composite with a range behind it. Don’t index: small tables the engine scans faster than it seeks; write-hot tables beyond the essential few; columns already served by an existing index’s left prefix; and anything you can’t name a query for — an index without a query is pure write tax. The audit loop: EXPLAIN the slow queries for scans, check usage stats for dead indexes, add and drop accordingly.

Common use cases

  • The hot-list query. Status + date filters behind every dashboard and feed — the composite index’s home turf.
  • Login and lookup fields. Email, username, external IDs — unique indexes doing constraint and speed at once.
  • Join and pointer paths. Every foreign key and pointer your queries traverse; the N+1 problem’s quiet accomplice is an unindexed one.
  • Multi-tenant filters. Tenant-leading composites — the multi-tenant architecture rule that every index starts with tenant_id.
  • Search and geo. Inverted and spatial indexes powering the queries B-trees can’t.

Should you add that index? A decision matrix

Add it when…Skip it when…
A frequent query filters or sorts on the columnNo production query uses it
EXPLAIN shows scans on a growing tableThe table is small and stays small
The column is selective (many distinct values)An existing index’s prefix already covers it
It’s a join key or pointer fieldThe table is write-dominated and the read is rare
A unique rule needs enforcing anywayYou’re guessing — measure first

The discipline in one sentence: indexes are created in response to queries, reviewed against usage, and dropped without sentimentality.

Limitations and trade-offs

  • Writes pay for reads. Every index is another structure each write must maintain — bulk loads famously run faster with indexes dropped and rebuilt.
  • Storage is real. Indexes commonly rival the table’s own size; covering indexes especially trade disk for speed.
  • The optimizer decides, not you. A low-selectivity index may be rightly ignored; stale statistics can wrongly ignore a good one — the plan is the truth.
  • Order mistakes neutralize composites. (created_at, status) and (status, created_at) are different tools; the leftmost-prefix rule forgives nothing.
  • Indexes can’t fix the query. Leading wildcards, functions over columns, and N+1 loops defeat indexing from above — query shape comes first.

Indexes on Back4app

Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Index management lives with the schema in the dashboard: create, review, and drop indexes per class — single-field or compound — on the MongoDB-backed store your SDK queries actually hit, following the same B-tree and ESR logic above. The query in the code tabs and the index that serves it are designed in the same place, which is precisely where the “index what you query” discipline wants to live.

Frequently asked questions

What is a database index in simple terms?

A separate, sorted structure — usually a B-tree — holding column values plus pointers to their rows, exactly like a book's index holds terms plus page numbers. The engine looks up the value in the small sorted structure and jumps straight to the matching rows, instead of reading the entire table hoping to find them.

How much faster is an indexed query?

The difference between logarithmic and linear work: a B-tree lookup touches a handful of pages whether the table holds ten thousand rows or a hundred million, while a full scan reads everything. At small sizes both feel instant — which is why missing indexes hide in development and detonate in production, where the same query suddenly examines millions of rows.

Do indexes slow down writes?

Yes — that is the tax. Every insert, update, or delete on an indexed column must also update each index that references it: six indexes on a table means up to six extra structure updates per write, plus the storage they occupy. Indexes are a read-speed purchase paid for in write speed and disk — deliberate ones earn it, forgotten ones just bill you.

Which columns should be indexed?

The columns your queries actually use: filters (WHERE), join keys — including foreign keys and pointer fields, which some engines do not index automatically — and sort columns (ORDER BY). Add selectivity to the test: an email column distinguishing millions of rows earns its index; a boolean flag that splits the table in half mostly does not.

What order should composite index columns be in?

Equality first, range and sort later — and remember the leftmost-prefix rule: an index on (a, b, c) serves queries filtering on a, on a and b, or on all three, but not on b alone. The document-database phrasing of the same rule is ESR: Equality, Sort, Range. Column order is the difference between a composite index working and merely existing.

How do I find missing or unused indexes?

For missing ones: run the query plan — EXPLAIN — and look for full scans on large tables; the filter column of a slow, frequent query is the candidate. For unused ones: every engine tracks index usage statistics, and an index no query has touched in months is pure write tax — drop it. The plan and the stats together are the whole methodology.

What are unique, partial, and covering indexes?

Specializations of the same structure: a unique index enforces no-duplicates as a constraint while speeding lookups; a partial index covers only rows matching a condition — small and fast for hot subsets like unshipped orders; a covering index contains every column a query needs, letting the engine answer from the index alone without visiting the table.

Does indexing work the same in document databases?

Conceptually identical — B-trees over field values — with the same trade-offs and the same leftmost-prefix logic under the ESR rule of thumb. Document stores add multikey indexes over array fields and geospatial variants, and the operational rule survives translation: index what you query, especially the pointer fields that joins and includes traverse.

Related terms

Compare with

Further reading

Ready to build your backend?

Start your project on Back4app in minutes — database, auth, APIs, and Cloud Code included. No credit card required.

Written and reviewed by Back4app Engineering, Back4app Engineering · Published 2026-07-26