Hook: A hash index is the database version of a locker with a secret code: if you know the exact code, you go straight to the right locker, but it is terrible when you need the lockers in order.
Question: What is a hash index in SQL, and when would you use one?
Answer: A hash index stores a hashed version of the indexed value so the database can jump quickly to matching rows for exact equality searches like WHERE email = 'a@x.com'. It is fast for = comparisons, but it does not help with ranges, sorting, or prefix searches because the values are not kept in order. In PostgreSQL, hash indexes are a real feature, but B-tree is still the default because it is more flexible.
Interview-Ready Answer: I think of a hash index as a lookup table for exact matches. The database runs the column value through a hash function, jumps to the bucket for that hash, and checks only the rows there, so equality lookups are usually close to constant time on average. The tradeoff is that hash indexes do not support range queries or ordering, and collisions can still happen, so I would use them only when the workload is mostly exact-match lookups and I do not need ORDER BY, BETWEEN, or prefix searches.
A hash index is an index structure built around a hash function, which is a deterministic way to turn a value like an email address or ID into a fixed-size number. The key idea is simple: values that are equal should map to the same hash, so the database can find exact matches without walking through a sorted tree. This is not a core SQL-standard feature; it is an engine feature. In PostgreSQL, for example, you can create one with USING HASH.
WHERE email = 'grace@example.com', the engine hashes the search value the same way and goes directly to the matching bucket.user_id, email, or session_token.| Aspect | Hash index | B-tree |
|---|---|---|
| Best for | Exact match | Many query types |
| Range queries | No | Yes |
| Ordering | No | Yes |
| Typical cost | Avg O(1) | O(log n) |
| Worst case | O(n) | O(log n) |
| Default choice | Rarely | Usually |
For most systems, B-tree wins as the default because it is versatile: it supports equality, ranges, sorting, and often index-only access patterns. Hash is narrower but can be good when the workload is almost entirely = comparisons.
Average lookup time is close to constant, but that does not mean magical speed in every case. If the table is huge and the data is not in memory, you still pay random I/O to read the bucket pages; the difference is that you read far fewer pages than a full scan. On a warm cache, the gain is usually fewer comparisons and less CPU work. On a million-row table with a well-distributed key, a successful equality lookup might touch one bucket page and maybe one overflow page; with bad collisions, it can get slower quickly.
Space usage depends on bucket count, key size, and overflow chains. Hash indexes can be efficient, but they are not free: more buckets and overflow pages mean more maintenance, and a poorly chosen index can waste space without helping the query plan.
BETWEEN, >, <, and sorted output are a bad fit because hashes destroy order.Memory in one line: if you know the exact code, a hash index takes you straight there; if you need order or a range, it is the wrong map.
Imagine a payment reconciliation service in an e-commerce app. The team stores tens of millions of payment records and frequently looks them up by provider_charge_id, which is an exact external reference from the payment gateway. A hash index on that column is a good fit because the application usually asks, show me the row for this exact charge id, not show me the last 100 charges in order.
Now for the bug: a developer sees the hash index and assumes it will also help a dashboard query like WHERE created_at > now() - interval '15 minutes'. It does not. The query gets slower as traffic rises, the database starts doing sequential scans, and the logs show long-running queries with Seq Scan and sort steps. Users notice delayed payment status updates, support tickets spike, and the on-call engineer finds that the index choice was based on the wrong mental model: hash indexes are for exact matches, not time windows or reports.
-- PostgreSQL demo: hash index for exact-match lookups.
-- This script is intentionally small, but it shows the key win and one failure path.
DROP TABLE IF EXISTS accounts;
CREATE TEMP TABLE accounts (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO accounts (email, status, created_at) VALUES
('ada@example.com', 'active', '2024-01-01 10:00+00'),
('grace@example.com', 'active', '2024-01-02 11:00+00'),
('linus@example.com', 'blocked', '2024-01-03 12:00+00'),
('maria@example.com', 'active', '2024-01-04 13:00+00');
-- Equality searches are the sweet spot: the same input hashes to the same bucket.
CREATE INDEX accounts_email_hash_idx ON accounts USING HASH (email);
-- Give the planner statistics so the demo is more realistic.
ANALYZE accounts;
-- For a tiny demo table, PostgreSQL may prefer a sequential scan.
-- Turning this off helps show the hash-index path for the exact-match query.
SET enable_seqscan = off;
EXPLAIN SELECT id, email FROM accounts WHERE email = 'grace@example.com';
SELECT id, email FROM accounts WHERE email = 'grace@example.com';
-- Edge case / failure path: hash indexes do not help with prefix or range filters.
-- The engine cannot use the hash index here because the values are not ordered.
EXPLAIN SELECT id, email FROM accounts WHERE email LIKE 'grace%';
SELECT id, email FROM accounts WHERE email LIKE 'grace%';
EXPLAIN SELECT id, email FROM accounts WHERE created_at >= TIMESTAMPTZ '2024-01-03 00:00+00';
SELECT id, email FROM accounts WHERE created_at >= TIMESTAMPTZ '2024-01-03 00:00+00';
RESET enable_seqscan;=? No. B-tree is often just as fast or faster in practice because it is heavily optimized, more cache-friendly for many workloads, and much more versatile.ORDER BY or MIN/MAX? No. Those operations need order, and hash indexes intentionally destroy order.LOWER(email), will a hash index on email help? Not by itself. The expression changes the search key, so you would need an index on the expression itself, and even then engine support matters.Common Mistakes:
Memory Hook: Hash index = locker code; B-tree = library shelf. If you know the exact code, the locker opens instantly. If you need the books in order, you need the shelf.
Cheat Sheet:
WHERE id = ? or WHERE email = ?.BETWEEN, >, <, ORDER BY, or prefix search.Practice Tasks:
EXPLAIN for equality versus range queries.ORDER BY or BETWEEN and explain why a hash index would not help it.