Indexes and Query Performance
Trade write cost for read speed, deliberately
Indexes turn table scans into point lookups — B-trees, composite, partial and expression indexes — each with a write-cost and a size budget.
What you will learn
- Explain how a B-tree index accelerates lookups and ranges
- Design composite indexes to match filter + sort predicates
- Choose partial and expression indexes for hot paths
What is this?
An index is a sorted structure (usually B-tree) mapping key values to row locations, letting the engine answer equality and range predicates without scanning the table. Each index taxes writes (maintenance) and disk; good indexing is a deliberate trade of storage/write cost for read latency.
Why do you need it?
- A Seq Scan over 40M rows pays for a report someone clicks daily — one good index removes it.
- Composite and partial indexes shrink that cost further when predicates are predictable.
- Unused or duplicate indexes are pure write-tax — a defensible index set is a measured one.
Mental model
An encyclopedia's index volume
A database index
The index lists topics and page numbers so you don't read every page to find 'volcano'. It costs extra shelves (storage) and time to write (maintain), but lookups become instant.
How it works
- B-tree balance keeps inserts/lookups at O(log n) regardless of table order.
- Equality on the leading column uses the index directly; range scans walk sorted leaves.
- Composite (a, b) serves predicates on a, and a+b together — but leading-column absence degrades it.
- Partial indexes index a WHERE subset; expression indexes index func(column) for computed filters.
- The planner chooses indexes via cost; ANALYZE statistics keep cost estimates correct.
Indexing decision flow.
Key commands
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);Build an index without blocking writes.
ANALYZE orders;Refresh planner statistics after bulk loads.
pg_stats... / SHOW INDEX FROM orders;Inventory what already exists.
Examples
From Seq Scan to Index Scan
-- before: Seq Scan on orders (cost goes up with rows)EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending' AND user_id = 42; CREATE INDEX idx_orders_status_user ON orders (status, user_id);-- after: Index Scan using idx_orders_status_userA composite index on the exact predicate pair lets the planner use the index directly.
In production
- A customer-360 report's slow query gets a composite (customer_id, created_at) index, cutting p95 from 4s to 40ms.
- A partial index on active-only rows serves a hot 'pending orders' view that is 1% of the table.
- A unique index backs the natural-key dedup so duplicated loads fail at insert, not at report time.
Best practices
- Index for the queries, not the columns: match the WHERE + ORDER BY shapes.
- Build with CONCURRENTLY (Postgres) in maintenance windows to avoid blocking writes.
- Refresh ANALYZE after bulk loads so the planner sees fresh cardinality.
- Audit yearly with pg_stat_user_indexes to drop unused and duplicated indexes.
Common mistakes
- Indexing every column 'just in case', then paying write-tax and disk for nothing.
- Leading-column mismatch: index (a, b) used with only b in the predicate is useless.
- Forgetting expression indexes when filters are on lower(email) or extract(month).
- Building on 100M rows without CONCURRENTLY, locking the table through the build.
Common issues
Index created but EXPLAIN still Seq ScansCardinality stats stale, or the predicate shape differs (func(col), leading column missing)
ANALYZE the table, then reassess the predicate/column order.
Write-heavy table slowed after indexingToo many maintenance-heavy indexes on an insert-heavy store
Measure write duration before/after and drop rarely-used indexes.
Debugging tips
- When the planner still scans despite an index, check ANALYZE freshness and predicate forms.
- Inspect pg_stat_user_indexes for idx_scan counts — zero scans = dead weight.
- Look at FROM, WHERE and ORDER BY to derive the minimal composite columns in the right order.
Interview checkpoint
Hands-on lab
- Generate a large seeded table
- Observe a Seq Scan in EXPLAIN QUERY PLAN
- Create a composite index and confirm an Index Scan
Create a Composite Index and Prove It with EXPLAIN
30 min · advanced
Quick quiz
An index (a, b) most directly accelerates which predicate set?
Every index added to a write-heavy table costs write performance.