100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Architecture

Database Indexing at Scale

Explains how indexes accelerate queries via data structures like B-trees, and the tradeoffs of adding, maintaining, and choosing indexes on very large tables.

Databases at ScaleIntermediate9 min readJul 9, 2026
Analogies

Database Indexing at Scale

An index is an auxiliary data structure that lets a database find rows matching a condition without scanning the entire table. Without an index, a query like WHERE email = 'x@y.com' on a hundred-million-row table forces a full table scan — reading every row to check if it matches, which is prohibitively slow at scale. Indexes trade extra storage and write overhead for dramatically faster reads on the columns they cover, and choosing which columns to index (and how) is one of the highest-leverage decisions in database performance tuning.

🏏

Cricket analogy: Without an index, finding every ball a specific bowler bowled in a decade of matches means scanning every single delivery ever recorded, like a scorer flipping through every scorebook page by hand; an index lets them jump straight to that bowler's entries, at the cost of maintaining an extra card catalog.

B-tree indexes: the default workhorse

Most relational databases default to B-tree (or B+tree) indexes, a balanced tree structure that keeps data sorted and allows lookups, range scans, and ordered traversal in logarithmic time — O(log n) instead of O(n). Because the tree stays balanced as data is inserted and deleted, lookup performance degrades gracefully even as a table grows into the billions of rows, which is why B-trees remain the default choice for primary keys, foreign keys, and any column frequently used in WHERE, ORDER BY, or JOIN clauses.

🏏

Cricket analogy: A B-tree index is like a well-organized scorecard binder sorted by player name, letting a statistician find any player's record in a handful of flips rather than paging through the whole binder — and it stays quick to search even as decades of matches are added.

Other index types

Hash indexes offer O(1) average lookup for exact-match equality queries but can't support range queries (WHERE price > 100) since they don't preserve order. Composite (multi-column) indexes speed up queries that filter on multiple columns together, but column order matters enormously — an index on (country, city) speeds up queries filtering by country alone or by both, but not by city alone. Specialized indexes exist for specific data shapes: GiST/GIN indexes for full-text search and JSON containment, and geospatial indexes (R-trees) for location-based queries.

🏏

Cricket analogy: A hash index is like an instant name-to-locker lookup for players — fast for finding one exact player but useless for 'all players batting between 20-40' range queries; a composite index like (team, player) speeds up team-then-player lookups but not player-alone, and geospatial indexes help find 'grounds within 50km' queries.

The write-amplification cost

Every index must be updated on every insert, update, or delete that touches an indexed column, so adding indexes is not free — it slows down writes and consumes additional storage, sometimes multiple times the size of the underlying table. At scale, teams must be deliberate: index the columns that dominate query patterns (foreign keys, frequently filtered columns), and avoid indexing columns that are rarely queried or that change extremely frequently, since the write overhead there rarely pays for itself.

🏏

Cricket analogy: Every added scorecard index must be re-sorted on every ball bowled, slowing down live scoring, so scorers index only the columns actually queried often (bowler, batsman) and skip indexing rarely-checked fields like weather notes that would just add overhead without payoff.

sql
-- Composite index: column order determines which query patterns benefit
CREATE INDEX idx_orders_user_status ON orders (user_id, status);

-- Benefits from the index (uses leftmost prefix):
SELECT * FROM orders WHERE user_id = 42;
SELECT * FROM orders WHERE user_id = 42 AND status = 'shipped';

-- Does NOT benefit (status alone is not a prefix of the index):
SELECT * FROM orders WHERE status = 'shipped';

-- EXPLAIN shows whether the planner chose an index scan or a full table scan:
EXPLAIN SELECT * FROM orders WHERE user_id = 42;
-- Index Scan using idx_orders_user_status  (cost=0.42..8.60 rows=3)

At very large scale, some systems use covering indexes — indexes that include every column a query needs — so the database can answer the query entirely from the index without a second lookup ('bookmark lookup') into the base table, roughly halving I/O for that query.

Over-indexing is a real anti-pattern: teams sometimes add an index for every column 'just in case,' which bloats storage and silently degrades write throughput on a hot table without providing proportional read benefit, since only a fraction of those indexes are ever actually used by the query planner.

  • Indexes let the database avoid full table scans, turning O(n) lookups into roughly O(log n) with a B-tree.
  • B-trees are the default index type, supporting equality, range queries, and ordered traversal.
  • Hash indexes are faster for pure equality lookups but cannot serve range queries.
  • Composite index column order matters: it must match the leftmost prefix of the query's filter columns to be used.
  • Every index adds write overhead and storage cost, so indexes should be added deliberately based on actual query patterns.
  • Covering indexes can satisfy a query entirely from the index, avoiding a second lookup into the base table.

Practice what you learned

Was this page helpful?

Topics covered

#Architecture#SystemDesignStudyNotes#SoftwareEngineering#DatabaseIndexingAtScale#Database#Indexing#Scale#Tree#SQL#StudyNotes#SkillVeris