Intermediate · SQL in depth · topic 24 of 54 · 18 min
Indexes: making queries fast
Explanation
Why queries get slow
Without help, WHERE phone = '9810012345' makes the database read every row and test it. That is called a full table scan. With 500 rows nobody notices. With 50 lakh rows, every login or search reads 50 lakh rows, and the app crawls.
A database index is that sorted list. Most databases store it as a B-tree, a structure that stays sorted as you add rows, so finding one value among crores of rows takes only a handful of steps. Each entry points back to the full row.
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- now this can jump straight to customer 2's orders
SELECT id, amount FROM orders WHERE customer_id = 2;UNIQUE indexes
A UNIQUE index does two jobs: it speeds up lookups and it refuses duplicates. It is how you guarantee one account per email or phone, even when two signup requests arrive at the same moment, which a check in your JavaScript code cannot promise. A PRIMARY KEY or a UNIQUE column constraint creates one for you automatically.
CREATE UNIQUE INDEX idx_customers_phone ON customers(phone);
INSERT INTO customers (name, phone) VALUES ('Aarav', '9810012345');
INSERT INTO customers (name, phone) VALUES ('Someone', '9810012345');
-- Error: UNIQUE constraint failed: customers.phoneComposite indexes and column order
A composite index covers several columns, sorted by the first column, then by the second inside it, like a phone book sorted by surname and then first name. An index on (customer_id, order_date) helps queries that filter on customer_id, or on customer_id and order_date. It cannot help a query that filters only on order_date, just as a surname-first phone book cannot find everyone named "Priya".
| Query on orders | Can use index (customer_id, order_date)? |
|---|---|
| WHERE customer_id = 2 | Yes |
| WHERE customer_id = 2 AND order_date >= '2026-03-01' | Yes, best case |
| WHERE customer_id = 2 ORDER BY order_date | Yes, and no extra sort needed |
| WHERE order_date >= '2026-03-01' | No, first column is missing |
Checking the plan
Ask the database how it will run a query. In SQLite write EXPLAIN QUERY PLAN before the query: SCAN means it reads the whole table, SEARCH ... USING INDEX means it jumps. In PostgreSQL use EXPLAIN (the plan) or EXPLAIN ANALYZE (actually runs it and shows real times); look for Seq Scan versus Index Scan.
sqlite> EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 2;
-- before the index:
SCAN orders
-- after CREATE INDEX idx_orders_customer:
SEARCH orders USING INDEX idx_orders_customer (customer_id=?)The cost, and when not to index
- Every INSERT, UPDATE and DELETE must also update every index on the table, so too many indexes slow writes.
- Indexes take disk space and memory.
- Tiny tables (a few hundred rows) are fast to scan anyway.
- Columns with very few different values (status with 3 values, a true/false flag) rarely help alone.
- A function on the column, like WHERE LOWER(email) = ..., cannot use a plain index on email.
Watch the lesson
Indexes: making queries fast · 1/7
Queries that are fast with a hundred rows can be painfully slow with a crore rows. Indexes fix that.
Prefer a video by a teacher? Find free videos on “database indexes explained b-tree explain query plan” on YouTube ↗
In real life: OTP login by phone number
A grocery app with 80 lakh users logs people in by phone number. Without an index, every OTP request scans the whole users table, and at the evening rush the database CPU hits 100 percent. The fix is one line: CREATE UNIQUE INDEX on users(phone). Lookups drop from seconds to under a millisecond, and as a bonus the database now refuses a second account with the same phone, which also closes a bug where double-tapping "Sign up" created two users.
CREATE UNIQUE INDEX idx_users_phone ON users(phone);Practice (0/3 done)
Table: customers(id, name, phone), 4 rows with different phones. Step 1: create a UNIQUE index named idx_customers_phone on customers(phone). The starter then tries INSERT OR IGNORE of a customer whose phone already exists (OR IGNORE means: if a unique rule is broken, skip the row quietly instead of failing) and counts the customers. With your index the duplicate is skipped and the count is 4.
Table: orders(id, customer_id, order_date, amount, status). The app's slowest query is WHERE customer_id = ? AND order_date >= ?. Create a composite index named idx_orders_customer_date with customer_id first and order_date second. The query in the starter lists the index's columns in order, to check your work.
A users table has 50 lakh rows. The app runs these often: login by email, list a user's orders (orders.user_id), and an admin report filtered by is_active (true or false). Which indexes are the best first choice?
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.