Hook: Interviewers love this one because a B-tree index is the everyday tool that turns a slow table scan into a fast, sorted shortcut.
Question: What is a B-tree index in SQL, and why does it improve performance?
Answer: A B-tree index is a sorted data structure that lets the database find rows without reading the whole table. It keeps keys balanced, so search, range filters, and ordered reads are fast. In many SQL engines, the concrete structure is really B+tree-like, meaning the leaf pages are linked so nearby values can be read in order.
Interview-Ready Answer: I think of a B-tree index as a library catalog for a table. Instead of scanning every row, the database compares the key at a few levels, lands on the right leaf page, and then walks nearby entries in order. That is why B-tree indexes are the default general-purpose index in systems like PostgreSQL: they work well for equality lookups, ranges, and ORDER BY.
A B-tree is a balanced tree, which means every search path has about the same length. A page is a fixed-size block of storage, often 8 KB in PostgreSQL, and a fan-out is how many child pointers fit in one internal page. Big fan-out is the secret sauce: even with millions of rows, the tree is usually only a few levels deep.
BETWEEN or >=, it scans forward through linked leaf pages instead of jumping back to the root each time.Use a B-tree when you need equality filters, range filters, prefix searches that match the left side of the key, sorting, or fast joins on indexed columns. In practice, this is the everyday index for customer IDs, timestamps, email addresses, and foreign keys.
| Option | Best for | Weak spot |
|---|---|---|
| B-tree | Equality, range, ORDER BY | Write overhead |
| Hash | Equality only | No range/order use |
| Seq scan | Small tables | Reads everything |
DATE(created_at), often blocks normal index use unless you create an expression index.NULL values, but ordering details and planner behavior vary by database version and vendor.The mental model to keep: a B-tree is not magic; it is a very organized shortcut that keeps the search space small and sorted.
Imagine a checkout service for an e-commerce site. The app needs to find recent orders by customer_id and sort them by created_at, so the team adds a B-tree index on (customer_id, created_at). That makes customer support screens and fraud checks fast because the database can jump straight to one customer’s slice of the data.
Then a developer changes a query to WHERE DATE(created_at) = CURRENT_DATE because it looks cleaner. Suddenly the index is much less useful, the database starts scanning far more rows, CPU climbs, and the p95 latency jumps from milliseconds to seconds. The logs show slow query entries, the dashboard shows more sequential scans, and users feel it as a laggy checkout or a timeout on order history pages.
What went wrong: the team forgot that B-tree indexes work best when the predicate matches the stored order of the key. A small-looking function wrapper changed the access pattern enough to defeat the shortcut.
-- PostgreSQL-flavored SQL example: B-tree indexes are the default general-purpose index.
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
customer_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
last_name TEXT NOT NULL,
first_name TEXT NOT NULL,
city TEXT NOT NULL
);
INSERT INTO customers (last_name, first_name, city) VALUES
('Adams', 'Amy', 'Boston'),
('Brown', 'Ben', 'Austin'),
('Brown', 'Liam', 'Denver'),
('Chen', 'Mia', 'Seattle'),
('Garcia', 'Noah', 'Miami'),
('Lopez', 'Zoe', 'Chicago'),
('Nguyen', 'Linh', 'San Jose'),
('Patel', 'Isha', 'Dallas'),
('Smith', 'Olivia','New York'),
('Smith', 'Emma', 'Portland');
-- This B-tree keeps rows sorted by (last_name, first_name).
-- Good for equality on last_name, range filters on last_name, and ORDER BY last_name, first_name.
CREATE INDEX idx_customers_last_first ON customers (last_name, first_name);
-- Good fit: the search starts with the leading indexed column.
EXPLAIN (COSTS OFF)
SELECT customer_id, last_name, first_name
FROM customers
WHERE last_name = 'Brown'
ORDER BY first_name;
-- Good fit: a range on the indexed key can walk neighboring leaf entries.
EXPLAIN (COSTS OFF)
SELECT customer_id, last_name, first_name
FROM customers
WHERE last_name >= 'C' AND last_name < 'M'
ORDER BY last_name, first_name;
-- Edge case: filtering only on the second column usually cannot use this composite index well.
-- In a larger table, the planner may choose a sequential scan or need a separate index on first_name.
EXPLAIN (COSTS OFF)
SELECT customer_id, last_name, first_name
FROM customers
WHERE first_name = 'Liam';
-- Another edge case: a leading wildcard prevents normal ordered prefix search.
-- A B-tree cannot jump to the middle of every possible string suffix.
EXPLAIN (COSTS OFF)
SELECT customer_id, last_name, first_name
FROM customers
WHERE last_name LIKE '%son';
-- If you need to search by first_name often, create another index with the right leading column.
CREATE INDEX idx_customers_first_name ON customers (first_name);
EXPLAIN (COSTS OFF)
SELECT customer_id, last_name, first_name
FROM customers
WHERE first_name = 'Liam';Follow-up & Tricky Questions:
Common Mistakes:
Memory Hook: A B-tree is a library catalog: each card points to the right drawer, the drawers stay sorted, and you never read the whole library just to find one book.
Cheat Sheet:
Practice Tasks:
EXPLAIN.LIKE 'abc%' and then LIKE '%abc' to see why prefix searches are index-friendly but suffix searches are not.