Hook: Interviewers love Hash Join because it reveals whether you understand how SQL engines match rows fast without scanning everything twice.
Question: What is a hash join in SQL?
Answer: A hash join is a way for the database to combine two tables by building a hash table from one side, then looking up matching rows from the other side. A hash table is a lookup structure that groups rows by join key, so matches can be found quickly. It is especially good for equality joins like a.id = b.id when one input is small enough to fit in memory.
Interview-Ready Answer: I think of a hash join as a two-step join strategy: first I build a hash table from the smaller input using the join key, then I probe it with rows from the other input to find matches. That gives expected linear performance, roughly O(build + probe), instead of comparing every row to every other row. It is a great choice for equality joins, but if the hash table does not fit in memory, the engine may spill to disk and get slower.
Detailed Explanation: A hash join is a join algorithm, meaning it is one of the engine's internal ways to execute a JOIN. Instead of comparing every row from table A with every row from table B, the engine turns one input into a hash table keyed by the join column. Then it scans the other input and uses the same key to jump directly to the matching bucket.
LEFT JOIN, unmatched probe rows still come out with NULLs on the right side.a.date < b.date; hash joins are built for equality, not ordering.| Method | Best for | Big idea | Main risk |
|---|---|---|---|
| Hash join | Equality joins | Build hash, then probe | Memory spill |
| Nested loop | Small inputs | Compare row by row | Can become O(N×M) |
| Merge join | Sorted inputs | Walk both lists in order | Needs sort/order |
Expected time is roughly O(B + P), where B is build rows and P is probe rows. Space is roughly O(B) for the hash table. In real systems, if the build side fits in memory, a hash join can be extremely fast; if it spills into many batches, the same query can become much slower because of disk reads and writes. A classic interview detail: the optimizer depends on statistics, so stale row counts can make it pick the wrong build side or estimate memory badly.
NULL never matches NULL in standard SQL.Memory model: think “make a phone book from the smaller side, then look up every caller.” That is the whole mental model you want on a whiteboard.
Real-World Story: Imagine a checkout service in an e-commerce platform that joins orders to customers for a nightly revenue report. The report is fast when the optimizer builds the hash table from the smaller customers table and probes it with millions of orders.
One night, a schema change and stale statistics make the optimizer underestimate the size of customers. It chooses a bad plan, the hash table grows too large, and the engine starts spilling to temp files. The dashboard slows from seconds to minutes, logs show hash batching or temp-file activity, and analysts see timeouts instead of fresh revenue numbers.
What went wrong from a learning point of view? The team thought “hash join = always fast,” but the real rule is “hash join is fast when the build side is small enough to stay in memory and the join is an equality match.” That is why production DBAs care so much about up-to-date statistics and memory settings.
-- PostgreSQL demo: force a hash join so you can see the plan shape clearly.
-- The setup uses tiny temp tables, so it is safe to run in a scratch session.
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS departments;
CREATE TEMP TABLE departments (
dept_id INT PRIMARY KEY,
dept_name TEXT NOT NULL
);
CREATE TEMP TABLE employees (
emp_id INT PRIMARY KEY,
emp_name TEXT NOT NULL,
dept_id INT
);
INSERT INTO departments (dept_id, dept_name) VALUES
(10, 'Sales'),
(20, 'Engineering'),
(30, 'Finance');
INSERT INTO employees (emp_id, emp_name, dept_id) VALUES
(1, 'Ava', 20),
(2, 'Ben', 10),
(3, 'Cara', 20),
(4, 'Dev', NULL), -- edge case: NULL does not match any department
(5, 'Eli', 99); -- edge case: no matching department row
-- These planner settings are PostgreSQL-specific and make the hash join easy to observe.
-- The engine may otherwise choose another valid join method on tiny tables.
SET enable_nestloop = off;
SET enable_mergejoin = off;
-- INNER JOIN: only rows with matching dept_id values survive.
EXPLAIN (ANALYZE, BUFFERS)
SELECT e.emp_id, e.emp_name, d.dept_name
FROM employees AS e
JOIN departments AS d
ON e.dept_id = d.dept_id
ORDER BY e.emp_id;
SELECT e.emp_id, e.emp_name, d.dept_name
FROM employees AS e
JOIN departments AS d
ON e.dept_id = d.dept_id
ORDER BY e.emp_id;
-- LEFT JOIN: unmatched employees still appear, but the department columns become NULL.
EXPLAIN (ANALYZE, BUFFERS)
SELECT e.emp_id, e.emp_name, d.dept_name
FROM employees AS e
LEFT JOIN departments AS d
ON e.dept_id = d.dept_id
ORDER BY e.emp_id;
SELECT e.emp_id, e.emp_name, d.dept_name
FROM employees AS e
LEFT JOIN departments AS d
ON e.dept_id = d.dept_id
ORDER BY e.emp_id;
-- If you changed the join condition to a range predicate like e.dept_id < d.dept_id,
-- a hash join would no longer be the right fit because hash joins are built for equality matching.Follow-up & Tricky Questions:
NULL.ORDER BY.<? Not as the primary join condition. Hash joins are for equality matching; range conditions usually need nested loop or merge join logic.Common Mistakes:
ORDER BY when you need it.Memory Hook: “Build a bucket, then probe the bucket.” Picture one table being sorted into labeled mail slots, then the other table walking past and checking the same label to find a match.
Cheat Sheet:
O(B + P).Practice Tasks:
Hash Join.LEFT JOIN and identify which rows become NULL-extended.