What you'll learn
Quick Answer
An index is a sorted structure that lets the database find rows without scanning the whole table. Index the columns you filter and join on. Every index slows down writes, so do not add them everywhere, and wrapping an indexed column in a function stops the index being used.
What an index actually is
Without an index, finding every student on course 3 means reading every row in the table and checking. That is a full table scan. With a thousand rows it is instant. With ten million it is not.
An index is a separate sorted structure — usually a B-tree — mapping values to the rows that contain them. Because it is sorted, the database can find the entry by repeated halving rather than by looking at everything, and then jump straight to the matching rows.
CREATE INDEX idx_student_course ON student(course_id);
The textbook analogy is genuinely the right one: the index at the back of a book. Without it you read every page looking for a word. With it you look the word up and turn to the pages listed. The index takes extra space, and it has to be reprinted whenever the book changes — which is exactly the trade-off a database index makes.
What to index
Index the columns that appear in these places:
- WHERE clauses — the columns you filter on.
- JOIN conditions — foreign key columns especially. Normalizing your schema creates joins, and unindexed foreign keys are the most common cause of slow ones.
- ORDER BY — an index can supply rows already in order, avoiding a sort.
Primary keys are indexed automatically. Foreign keys often are not, depending on the database, and that surprises people — it is worth checking rather than assuming.
Columns with few distinct values are poor candidates. An index on a boolean or a gender column rarely helps, because half the table matches and the database may reasonably decide scanning is cheaper.
Why not index everything
Because every index must be kept up to date. An INSERT into a table with six indexes writes the row once and updates six separate structures. The same applies to UPDATE and DELETE.
So indexes trade write speed and disk space for read speed. On a table that is written constantly and read rarely, over-indexing makes the system slower overall. On a table read constantly and written rarely, indexes are close to free.
The practical approach is to add indexes in response to measured slow queries, not in advance. "Add an index to every column" is a real anti-pattern with a real cost, and being able to explain that trade-off is a strong interview answer.
How queries silently defeat an index
The index exists, the query looks right, and the database scans the whole table anyway. Usual causes:
- A function around the column.
WHERE YEAR(created_at) = 2026cannot use an index oncreated_at, because the index stores dates, not years. Rewrite as a range:WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'. - A leading wildcard.
LIKE '%son'cannot use an index, because a sorted structure cannot help when you do not know the beginning.LIKE 'John%'can. - Type mismatch. Comparing a numeric column to a quoted string may force a conversion on every row.
- Wrong column order in a composite index. An index on
(a, b)helps queries filtering ona, or onaandb— but generally notbalone. Order matters, like sorting a phone book by surname then first name.
Use EXPLAIN before your query to see what the database actually plans to do. It will tell you whether the index is used, and it removes the guesswork entirely. Most developers never run it; the ones who do find these problems in minutes.
Seeing it for yourself
The effect is invisible on small data, which is exactly why it bites in production. To feel it, generate a table with a few hundred thousand rows, time a filtered query, add the index, and time it again. The difference is not subtle.
Then run EXPLAIN on both and compare the plans. Seeing "full table scan" become "index lookup" makes the concept concrete in a way that reading about B-trees does not.
This pairs directly with normalization: splitting tables properly creates joins, and indexing the join columns is what keeps those joins fast.
