Developer

How Database Indexes Make Queries Fast

The data structure that turns a full-table scan into a millisecond lookup

By Kiran · 8 min read · 23-07-2026

If you have ever watched a query crawl for thirty seconds and then return a single row, you already understand the problem an index solves. To really grasp how database indexes work, it helps to start with what the database is forced to do without one, and then see how a single well-placed structure turns that slow crawl into a lookup that finishes before you lift your finger off the enter key. Indexes are the main reason a query over ten million rows can come back in a few milliseconds instead of grinding through the whole table.

This is a practical tour: why unindexed queries get slow, what an index actually is on disk, how the B-tree behind most indexes keeps lookups fast, and where indexes cost you. No jargon for its own sake, just the mental model you need to make good calls.

The problem: a full table scan

Imagine a users table with five million rows and a query like SELECT * FROM users WHERE email = '[email protected]'. Without an index, the database has no idea where that row lives. Its only option is a full table scan: read every row, compare the email, and keep going until it either finds a match or runs out of table.

The cost of that scan grows in a straight line with your data. Ten times more rows means roughly ten times more work. It might feel instant on a laptop with a thousand test rows, then fall over in production at five million. Nothing changed in your code. The table just got big enough for the missing index to matter.

What an index actually is

The word "index" is borrowed from books, and the metaphor is almost exact. The index at the back of a textbook is a sorted list of terms, each pointing to the pages where that term appears. You do not read the whole book to find one topic; you jump to the index, find the term in seconds because it is alphabetized, and go straight to the page.

A database index is the same idea. It is a separate, sorted data structure that stores the values of one or more columns along with a pointer to the full row on disk. Because the values are kept in order, the database can search them the way you search a sorted list, instead of reading them one by one. The trade is that the index is a second copy of that column's data, kept in sync automatically every time the table changes.

How database indexes work inside a B-tree

Most indexes you will ever create are B-tree indexes (technically a B+ tree in most engines). A B-tree is a balanced tree built to minimize disk reads, which is the expensive part of any database operation. It has three kinds of nodes: a single root at the top, internal nodes in the middle that route the search, and leaf nodes at the bottom that hold the indexed values and their row pointers.

Diagram of a three-level B-tree index with root, internal, and linked leaf nodes
A B-tree keeps millions of rows within three or four hops of the root.

The magic is in the fan-out. Unlike a binary tree, where each node has at most two children, a B-tree node can hold hundreds of keys and point to hundreds of children. That width keeps the tree extremely shallow. A B-tree indexing a hundred million rows is often only three or four levels deep. A lookup starts at the root, compares your value to the keys there to pick the right child, drops one level, compares again, and repeats until it reaches a leaf. Three or four comparisons, three or four disk reads, and you have the exact location of your row.

Why shallow means fast

This is the difference between linear and logarithmic cost. A full scan of a hundred million rows touches a hundred million rows. A B-tree lookup touches the height of the tree, which barely grows as the table gets bigger. Double the data and a scan doubles its work, while the tree might add a single level, or none at all. That is why an indexed lookup feels constant even as your table grows into the tens of millions.

There is a bonus baked into the structure. In a B+ tree the leaf nodes are linked together in sorted order, so once you find a starting point, you can walk sideways through the leaves. That makes range queries such as WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31' and ORDER BY clauses cheap, because the data is already sorted the way you asked for it.

The same query, with and without the index

The clearest way to feel the difference is to look at what the database planner decides to do. Every SQL engine has an EXPLAIN command that shows the plan for a query before it runs. Without an index you will see a sequential scan or full table scan over every row. Add the right index and the same query switches to an index seek that touches a tiny fraction of the table.

Comparison of a full table scan reading every row versus an index seek jumping to one row
Without an index the database reads every row; with one it jumps straight to the match.

Getting comfortable with EXPLAIN is the single highest-leverage habit for anyone writing SQL. It tells you the truth: whether your index is actually being used, whether the engine fell back to a scan, and whether it had to do extra sorting work. When a query is slow, do not guess. Run EXPLAIN, read the plan, and let it point you at the missing index.

Composite indexes and the leftmost-prefix rule

You can index more than one column at once. A composite index on (last_name, first_name) sorts entries by last name first, then by first name within each last name, exactly like a phone book. This is powerful, but it comes with a rule that trips up a lot of engineers: the leftmost-prefix rule.

An index on (a, b, c) can serve a query that filters on a, on a and b, or on all three. It cannot help a query that filters on b alone or on c alone, because the entries are only sorted by those columns after the earlier ones are fixed. It is like trying to find everyone named "Sam" in a phone book that is sorted by last name. First names are scattered everywhere, so the sort order gives you nothing.

That makes column order a real design decision, not a stylistic one. A few reliable guidelines:

  • Put columns used for equality filters (=) before columns used for ranges (<, >, BETWEEN).
  • Lead with the column that appears in the most queries, since a composite index can serve any leftmost prefix of itself.
  • Higher-cardinality columns, the ones with many distinct values, usually belong earlier because they narrow the search fastest.

Covering indexes: skipping the table entirely

Normally an index gets you to a row pointer, and the database then reads the full row from the table to fetch the other columns you asked for. A covering index skips that second step. If an index already contains every column a query needs, the engine can answer straight from the index and never touch the table. This is called an index-only scan, and it is one of the biggest quiet wins in query tuning.

For example, if you frequently run SELECT status FROM orders WHERE customer_id = ?, an index on (customer_id, status) covers it completely. The database finds the customer_id in the index and reads the status right there, with no trip back to the table. Fewer disk reads, faster query, same result.

The cost side: why you do not index everything

If indexes are this good, why not put one on every column? Because they are not free, and the price is paid on writes. Every index is a second copy of its data that has to stay correct, so every INSERT, UPDATE, and DELETE has to update the table and every affected index. A table with five indexes turns one write into six. On a write-heavy or logging table, that overhead adds up quickly and can become the bottleneck.

Indexes also take real disk space. On a large table, the total size of its indexes can rival or exceed the size of the table itself, which inflates storage, backups, and memory pressure. A few habits keep this in check:

  • Index the columns that actually show up in WHERE clauses, JOIN conditions, and ORDER BY, not every column just in case.
  • Be cautious with low-cardinality columns like a boolean is_active flag. An index that only splits the table in half rarely beats a scan.
  • Watch for redundant indexes. If you have a composite index on (a, b), a separate index on a alone is usually wasted space.
  • Periodically look for unused indexes. An index nobody queries is pure write and storage overhead with no upside.

A short checklist for indexing well

Boiled down, good indexing comes from a handful of questions. Which queries are actually slow, according to real measurement rather than a hunch? What do those queries filter, join, and sort on? Does a composite index in the right column order cover the important ones? And is each index earning its keep against the write cost it adds? Answer those honestly and you avoid both extremes: the unindexed table that scans everything, and the over-indexed table that spends all its time maintaining indexes nobody uses.

When you are documenting a schema or sketching a system for your team, a clean visual makes these ideas land faster than a wall of DDL. If you build architecture diagrams or an internal dashboard, Thridy's soft-3D server icon and the wider 3D icon gallery are a tidy way to label the moving parts, and the category index makes it easy to find the right glyph for databases, storage, and networking.

Indexes are not magic. They are a sorted copy of your data, shaped into a shallow tree so the database can jump instead of scan. Understand that one idea and most of the day-to-day decisions, which column, what order, when to add one and when to leave it out, stop being guesswork and start being obvious.

Key takeaways

  • Without an index, the database runs a full table scan whose cost grows in a straight line with your data.
  • An index is a sorted copy of your columns shaped into a shallow B-tree, so lookups stay fast even at millions of rows.
  • Column order in a composite index matters: only leftmost prefixes of the index can be used.
  • A covering index answers a query straight from the index with no trip back to the table.
  • Indexes cost you on writes and storage, so index the columns that actually appear in WHERE, JOIN, and ORDER BY.

Questions people ask

How do database indexes make queries faster?

An index is a separate, sorted copy of one or more columns with pointers to the full rows. Because the values are ordered inside a B-tree, the database can jump to the right location in a few hops instead of scanning every row, which turns a linear-cost search into a logarithmic one.

What is a B-tree index?

A B-tree is a balanced tree with a root, internal routing nodes, and leaf nodes that hold the indexed values and row pointers. Each node holds many keys, so the tree stays shallow, often only three or four levels deep even for a hundred million rows, and a lookup reads just one node per level.

What is the leftmost-prefix rule for composite indexes?

A composite index on (a, b, c) can serve queries that filter on a, on a and b, or on all three, but not on b or c alone. The entries are only sorted by later columns once the earlier ones are fixed, so the order you list columns in is a real design decision.

Why not put an index on every column?

Indexes cost you on writes and storage. Every index is a second copy of its data that must be updated on every insert, update, and delete, so a table with five indexes turns one write into six. Index the columns used in WHERE, JOIN, and ORDER BY clauses, not every column just in case.

How do I check whether my query is using an index?

Run EXPLAIN on the query. It shows the planner's chosen strategy: a sequential or full table scan means no useful index was used, while an index seek or index-only scan means the index is working. It also reveals extra sorting steps that hint at a better index.

Sources

This article was produced with the help of generative AI, drawing on the sources above, and reviewed by a human before publishing. All 3D icons shown are original artwork from Thridy. If you spot anything that needs a correction, let us know.

Enjoyed this article?

Subscribe to the Thridy Journal. One great story at a time, no noise.

Keep reading