Course topics

By WebNest Studio Engineering Team

Database and SQL Tutorial

Database Indexes

An index is a separate data structure that a database maintains alongside a table so it can find rows without scanning every single one. Without an index, looking up a row by a value means the database engine reads the entire table from start to end — a "full table scan" — checking every row against your condition. On a table with a few hundred rows this is instant; on a table with fifty million rows it can take minutes.

Most relational databases implement indexes using a B-tree (balanced tree) structure. A B-tree keeps indexed values in sorted order across a tree of pages, so the engine can jump directly to the relevant branch instead of reading everything, in roughly logarithmic time rather than linear time. Some databases also offer hash indexes, full-text indexes, or specialized indexes (like GIN/GiST in PostgreSQL for JSON or geometric data), but the B-tree is the default and most common case you will encounter.

Why it matters

A query that takes ten seconds on a large table can take ten milliseconds with the right index. An index is the first thing to check when a query is slow, and the most common fix. It is not free, though: every index slows down writes, so knowing where to add one, and where not to, is a core skill.

How a B-Tree Index Speeds Up Reads

Think of a B-tree index like the index at the back of a textbook: instead of reading every page to find "transactions", you look up "T" in the index and jump straight to the right pages. A database B-tree stores indexed column values in sorted order, with each node pointing to a range of child nodes. Searching for a value means comparing against a handful of nodes as you descend the tree, rather than comparing against every row. This is why an indexed lookup on a huge table can return in milliseconds while an unindexed lookup on the same table might take seconds or longer.

The Write-Side Cost

Indexes are not free. Every time you INSERT, UPDATE, or DELETE a row, the database must also update every index that covers the changed columns, to keep the index's sorted structure consistent with the table's actual data. A table with five indexes means every write potentially touches six data structures instead of one. This is why indexing is a trade-off, not something to apply blindly to every column.

  • Reads become faster — the engine can locate matching rows without scanning the whole table.
  • Writes become slower — every INSERT/UPDATE/DELETE must also maintain each affected index.
  • Storage grows — an index is extra data stored on disk, separate from the table itself.

When to Add an Index

Add an index on columns that are frequently used in WHERE clauses, JOIN conditions, ORDER BY clauses, or as foreign keys — especially on large, read-heavy tables. Avoid indexing columns that are rarely queried, columns on small tables (a full scan of 200 rows is already fast), or columns on tables with very heavy write traffic where the index-maintenance cost would outweigh the read benefit. Primary keys are indexed automatically by virtually every database; the real decision is about secondary indexes on other columns.

Examples

Creating an index on a frequently filtered column

SQL
-- A users table with 5 million rows, frequently filtered by email during login
CREATE INDEX idx_users_email ON users (email);

-- This query now uses the index instead of scanning all 5 million rows
SELECT id, name, email
FROM users
WHERE email = 'asha@example.com';
Output
id: 348221 | name: Asha Verma | email: asha@example.com
(1 row returned in ~2ms using idx_users_email, versus ~800ms with a full table scan)

A composite index supporting a common two-column filter

SQL
-- Orders are frequently queried by customer_id and then filtered by status
CREATE INDEX idx_orders_customer_status ON orders (customer_id, status);

SELECT id, total_amount
FROM orders
WHERE customer_id = 1029 AND status = 'PENDING';
Output
id: 88213 | total_amount: 149.99
id: 88450 | total_amount: 39.50
(2 rows returned quickly via idx_orders_customer_status)

Common Mistakes

  • Adding an index to every column "just in case," which slows down every write and wastes storage without a matching read benefit.
  • Expecting an index to help a query that filters on a function of the column (e.g. WHERE YEAR(created_at) = 2026) — a plain index cannot be used unless it is built to match the expression.
  • Forgetting that column order matters in a composite (multi-column) index — an index on (customer_id, status) helps queries filtering by customer_id alone or by both columns, but does not help a query that filters only by status.
  • Not indexing foreign key columns, which makes JOINs between related tables slow as both tables grow.

Exercise

Orders are looked up by customer constantly. Create an index named idx_orders_customer on customer_id, and a composite index named idx_orders_customer_status on customer_id and status. Then run the query the indexes are meant to serve: the ids of customer 1's paid orders, in order.

Expected output: id, 1, 3 (one per line)

SQL · starter code
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, status TEXT);
INSERT INTO orders (id, customer_id, status) VALUES
  (1, 1, 'paid'),
  (2, 2, 'paid'),
  (3, 1, 'paid'),
  (4, 1, 'pending');

-- TODO: index on customer_id

-- TODO: composite index on customer_id and status

-- TODO: ids of customer 1's paid orders, ordered by id
Show hints
  • The syntax is CREATE INDEX index_name ON table_name (column).
  • A composite index lists its columns in order, separated by commas.
Show solution
SQL · solution
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, status TEXT);
INSERT INTO orders (id, customer_id, status) VALUES
  (1, 1, 'paid'),
  (2, 2, 'paid'),
  (3, 1, 'paid'),
  (4, 1, 'pending');

CREATE INDEX idx_orders_customer ON orders (customer_id);

CREATE INDEX idx_orders_customer_status ON orders (customer_id, status);

SELECT id
FROM orders
WHERE customer_id = 1 AND status = 'paid'
ORDER BY id;

Quick quiz

1. What is the cost of adding an index?

  • A. Reads become slower
  • B. The table can no longer be updated
  • C. Queries must be rewritten
  • D. Inserts, updates and deletes become slower, and the index takes disk space
Show answer

D. Every write has to update each index on the table as well.

2. A composite index is on (last_name, first_name). Which filter can use it efficiently?

  • A. WHERE first_name = 'Asha'
  • B. WHERE last_name = 'Rao'
  • C. WHERE age = 30
  • D. None of them
Show answer

B. A composite index is usable from its leftmost column onwards.

3. Which data structure do most database indexes use?

  • A. A B-tree
  • B. A linked list
  • C. A stack
  • D. A plain array
Show answer

A. It keeps the keys sorted and finds a value in a small number of steps, even in a huge table.

Interview questions

What is an index and how does it speed up a query?

Show answer

An index is a separate, sorted structure, usually a B-tree, that maps the values of one or more columns to the locations of the matching rows. Without it, the database reads every row of the table to find matches, which is a full table scan. With it, the database walks the tree to the value in a number of steps proportional to the logarithm of the table size and reads only the matching rows.

Which columns should be indexed, and which should not?

Show answer

Index columns that appear often in WHERE conditions, join conditions and ORDER BY, and foreign key columns. Columns with many distinct values benefit most. Avoid indexing columns with very few distinct values, such as a boolean, small tables, and columns that are rarely used for searching. Tables that are written far more than they are read should have few indexes, since each one slows every write.

Key Points to Remember

  • An index is a separate sorted structure (typically a B-tree) that lets the database find rows without scanning the whole table.
  • Indexes speed up reads but slow down writes and use extra storage — every index must be maintained on INSERT, UPDATE, and DELETE.
  • Add indexes to columns used in WHERE, JOIN, and ORDER BY on large, read-heavy tables; avoid over-indexing small or write-heavy tables.
  • Composite index column order matters — it supports queries that filter on a matching left-to-right prefix of the indexed columns.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs your SQL on SQLite in your browser. Standard SQL works; a few features that exist only in PostgreSQL or MySQL do not. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering