1. What It Is
Before you add Redis or shard the database, ask whether the right index would fix the slow query. Indexing is how you turn full table scans into millisecond lookups β and how you accidentally slow down every write.
What:
An index is a secondary database access path built on top of primary tables.
Primary purpose:
Accelerating read query speeds by replacing slow full table disk scans with binary tree traversals.
Usually used for:
Filtering rows (WHERE), sorting datasets (ORDER BY), joining tables, and resolving point lookups.
2. Core Mental Model
An index is a sorted lookup structure β design columns to match the WHERE and ORDER BY clauses you actually run:
π Secondary Access Path
Instead of reading millions of database rows sequentially from disk, follow a tree branch directly to the row pointer in O(log N) steps.
βοΈ Write & Memory Tax
Indexes are not free. Every added index slows database mutations because the engine must update both the raw table and the index tree.
βοΈ Left-to-Right Sort Order
Composite (multi-column) indexes are sorted hierarchically. They only support queries matching from left to right (leftmost prefix rule).
In the room
Candidates propose caching before checking indexes. Walk through the query plan: "This lookup is O(n) without an index on user_id; a B-tree index makes it O(log n)." Mention composite index column order if the query filters on multiple fields.
3. Why It Matters in HLD
Indexes are how we turn full table scans into point lookups β but every index taxes writes. We frame the discussion around three lenses:
Needed When:
Query latencies spike, reads heavily outnumber writes, or database queries scan more than a small fraction of rows.
Avoids:
Full table scans, database disk I/O exhaustion, high query response variance, and CPU starvation.
Optimizes For:
Time to First Byte (TTFB), index-only reads (covering index), and stable response times under heavy concurrent loads.
4. Architecture & Data Flow
Walk the query path as interview steps. Step 1 β Parse: SQL arrives at the optimizer. Step 2 β Plan: optimizer chooses index seek vs sequential scan based on selectivity and stats. Step 3 β Index seek: B-tree traversal to matching rows β O(log n) instead of O(n). Step 4 β Covering index: if all SELECT columns live in the index, skip the heap fetch. Step 5 β Write path: every INSERT/UPDATE maintains index pages β name the write amplification.
5. Key Characteristics
These index types and optimizer behaviors are what we cite when sizing read vs write cost:
- Clustered Index: Organizes the raw table rows on disk physically sorted by the primary key (maximum one per table).
- Non-Clustered Index: A separate tree storing secondary keys pointing to primary key addresses.
- Partial Index: Indexes only rows matching a predicate (e.g.
WHERE status = 'ACTIVE') β smaller and faster for filtered hot paths. - Covering Index: Includes all columns in the
SELECTclause so the engine never touches the heap β enables index-only scans. - Leftmost Prefix Rule: A composite index on
(A, B, C)only helps queries that filter on columnAfirst. - Index type matrix β match structure to query pattern:
| Type | Lookup | Range | Use Case |
|---|---|---|---|
| B+Tree (General purpose) | O(log N) | Excellent (linked leaves) | Standard SQL/NoSQL primary/secondary indexes |
| Hash (Point lookup) | O(1) | None (unordered keys) | Redis, exact match filters in SQL databases |
| Covering (Index-only) | O(log N) | Excellent | Queries reading ONLY the indexed columns (zero disk row seek) |
| Inverted (Full-text) | Token-based | N/A | Search engines like Elasticsearch, PostgreSQL GIN/GiST |
| Geospatial (R-Tree / S2) | O(log N) | Proximity | Location queries near coords (PostGIS, Uber, MongoDB) |
In the room
When you propose an index, say what query it serves and what write cost you accept. "Add an index on user_id" without naming the SELECT is half an answer.
6. Strategic Tradeoffs
Indexes accelerate reads but slow writes β we articulate the trade-off:
| Benefit | Cost |
|---|---|
| Drastically Faster Reads (swaps slow full table scans for O(log N) lookups) | Slower Writes (every INSERT/UPDATE/DELETE must update all secondary indexes) |
| Efficient Range Queries & ORDER BY (linked leaf nodes keep data physically sorted) | Memory & Disk Overhead (large indexes consume RAM buffer space and storage) |
| Covering Index Optimization (serves select queries entirely from index metadata) | Maintenance Overhead (indexes require periodic defragmentation/rebuilding) |
7. Failure / Bottleneck Awareness
Missing indexes and over-indexing both kill production systems β we name the failure modes:
Problem: A developer builds a composite index on (first_name, last_name) but queries with WHERE last_name = 'Smith'. The database optimizer ignores the index and falls back to a slow full table scan.
Mitigation: Ensure query patterns align from left to right with indexed columns, or add separate indexes tailored to individual filters.
Problem: Standard pagination query LIMIT 20 OFFSET 50000 gets progressively slower. The database must traverse and discard 50,000 index entries to read the target 20.
Mitigation: Switch to cursor-based pagination (keyset pagination): WHERE id > :last_seen_id ORDER BY id LIMIT 20 to jump directly to the starting offset.
Problem: Querying WHERE LOWER(email) = 'user@example.com' on an indexed email column bypasses the index entirely because database runtime calculations modify keys during search.
Mitigation: Query with exact casing matching the index, or construct a specialized functional index: CREATE INDEX ... ON users (LOWER(email)).
8. Common HLD Usage
These patterns show where indexing changes the architecture conversation:
| Problem | Usage |
|---|---|
| WhatsApp Message Timeline | Composite index on (chat_thread_id, timestamp) for rapid chat histories |
| Uber Driver Dispatch Proximity | Geospatial index (S2 / Geohash cells) to find nearby drivers in milliseconds |
| E-commerce Faceted Search | Inverted index (Elasticsearch) to filter product catalogs by color, size, price |
| Instagram Feed Timeline Generation | Index on (user_id, created_at DESC) for cursor-based user home timelines |
| API Idempotency Key Validation | Unique B+Tree or Hash index on (idempotency_key) to intercept duplicate requests |
9. Decision Signals
Reach for index discussion when point lookups or sort-heavy queries dominate the hot path:
- Slow queries show high
rows_examinedin comparison torows_sent. - Your database reads far outnumber mutations (e.g. system read-heavy metadata lookup).
- A high volume of queries perform filters, aggregations, or strict sorting.
- You face relational joins (foreign keys must always be indexed to prevent slow join scans).
- You must guarantee domain integrity (e.g. enforcing email uniqueness).
11. Deep Dive (Optional)
In interviews, propose indexes on columns that appear in frequent WHERE, JOIN, and ORDER BY clauses β email for login, user_id on child tables, composite keys for timeline queries. For search beyond SQL, mention Elasticsearch or PostGIS synced via CDC with acceptable lag.
Clustered vs Non-Clustered Storage Mechanics
In clustered indexes (e.g., MySQL's InnoDB primary key), leaf nodes store the actual physical data row on disk. In secondary non-clustered indexes, the leaf nodes store the secondary search key and the primary key as a row pointer. To retrieve non-indexed columns, the database must traverse the secondary index tree, find the primary key, and then traverse the primary clustered index tree (double lookup or key lookup).
Composite Index Rule Mathematics
When planning multi-column composite indexes, you must adhere to the Equality-first, Sort-next, Range-last optimization math:
-- For Query:
WHERE status = 'ACTIVE' AND user_id = 100 AND created_at > '2024-01-01'
ORDER BY created_at DESC;
-- Best Composite Index columns:
(user_id, status, created_at)- Columns filtered with exact match (
=) must come first. - Columns utilized in sorting (
ORDER BY) come second. - Columns queried with range comparisons (
>, <, BETWEEN) must come last. Range columns break B+Tree index traversal chain for subsequent columns.
Covering Indexes & Index-Only Scans
A covering index contains all columns requested in the query. For example, if you index (user_id, status) and query: SELECT status FROM users WHERE user_id = 10, the database reads the index leaf node directly and returns the response. It completely skips accessing raw table data on disk, reducing I/O operations to near zero.
Query Execution Analysis (EXPLAIN)
To verify database optimizer decisions, prefix queries with EXPLAIN or EXPLAIN ANALYZE. High-performing queries will list:
type: reforeq_ref(using index lookup).Extra: Using index(confirming covering index).- Low
rowsscanned ratio (ideally close to returned count). - Avoid
Extra: Using filesortorExtra: Using temporary(indicates slow non-indexed in-memory sorting).
LSM-Tree Write Path (Cassandra, RocksDB, LevelDB)
B+Tree indexes optimize for read latency β every INSERT updates random pages on disk, which hurts write-heavy workloads. Log-Structured Merge (LSM) trees flip the model: writes append sequentially to an in-memory memtable and a write-ahead log; when the memtable fills, it flushes to immutable SST files on disk. Reads check memtable β bloom filter β SST layers (newest to oldest).
- Write amplification: Background compaction merges SST files β disk I/O continues after writes "complete."
- Read amplification: A key may exist in multiple SST layers until compaction runs; bloom filters skip absent keys cheaply.
- Interview fit: Choose LSM-backed stores for append-heavy telemetry, messaging metadata, and write-heavy KV β choose B+Tree OLTP when complex indexed reads dominate.
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.