Learn how B-Tree and Hash indexes work, when to add them, and how to avoid over-indexing.
Published April 2, 2025
An index is a data structure that lets the database find rows quickly without scanning every row in a table. Think of it like a book's index — instead of reading every page, you look up the term and jump directly to the page.
Most database indexes (PostgreSQL, MySQL, SQLite) use a B-Tree (Balanced Tree). The tree keeps keys in sorted order, allowing:
[30]
/ \
[10,20] [40,50]
/ | \ / | \
[5][15][25][35][45][55]
-- Single column index
CREATE INDEX idx_users_email ON users(email);
-- Composite index (order matters!)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- Partial index (index only active users)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- Covering index (includes extra columns to avoid table lookup)
CREATE INDEX idx_orders_cover ON orders(user_id) INCLUDE (total, status);
For a composite index (user_id, created_at), the index is useful for:
WHERE user_id = 5 ✅WHERE user_id = 5 AND created_at > '2024-01-01' ✅WHERE created_at > '2024-01-01' ❌ (cannot use index — leading column missing)Rule: The index can be used only if you filter on a prefix of the index columns.
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42 AND created_at > '2024-01-01';
-- Output:
-- Index Scan using idx_orders_user_date on orders
-- Index Cond: ((user_id = 42) AND (created_at > '2024-01-01'))
-- Actual rows=150, Execution Time=0.8ms
Watch for Seq Scan on large tables — that usually means a missing or unused index.
| Type | Use Case |
|---|---|
| B-Tree | Default. Equality, range, sort |
| Hash | Equality only. Faster point lookup |
| GIN | Full-text search, JSONB, arrays |
| GiST | Geometric data, fuzzy search |
| BRIN | Huge append-only tables (timestamps) |
gender (M/F) rarely helpsemail (nearly unique) is far more selective than one on country.LIKE 'abc%' can use a B-Tree index, but LIKE '%abc' cannot (leading wildcard).