Think of this as the database’s simplest but most honest join strategy: it checks one row against another until it finds a match. Interviewers love it because it reveals whether you understand that a SQL join is not just syntax — it is an execution plan choice.
Question: What is a nested loop join?
Answer: A nested loop join is a way to combine two tables by taking one row from the first table and comparing it to rows in the second table. If the join condition matches, the database outputs the combined row. It is simple, works for many kinds of join conditions, and becomes fast when the outer table is small or the inner table has a useful index.
Interview-Ready Answer: I think of a nested loop join as a row-by-row matching strategy. The database picks one input as the outer table, reads one row, and then searches the inner table for matching rows; if there is an index on the join key, that search can be very fast. It is often a great choice for small result sets, selective filters, or non-equality joins, but without an index it can degrade to a very expensive O(N×M) scan.
A nested loop join is the most direct join algorithm: for each row on the outer side, the database checks rows on the inner side until it finds matches. The word outer here means the driving input for the algorithm, not the SQL keyword OUTER JOIN.
ON predicate; if the condition is true, it emits the combined row.LEFT JOIN, if no inner row matches, the outer row is still returned with NULLs for the inner columns.Nested loop joins shine when the outer side is small, when the inner side has an index on the join key, or when the join condition is not a simple equality. A classic example is a query that starts with a few filtered customer rows and then looks up matching orders by indexed customer_id. They are also strong for EXISTS and semi-join style checks because the engine can stop after the first match.
| Join type | Best when | Main weakness |
|---|---|---|
| Nested loop | Small outer, indexed inner | Can be O(N×M) |
| Hash join | Equality joins | Needs memory for hash table |
| Merge join | Sorted inputs | Sorting can be costly |
Without an index, the cost is roughly O(N×M). For example, 1,000 outer rows and 100,000 inner rows can mean up to 100 million comparisons, which is why a badly chosen nested loop can be slow. With an index on the inner join key, the engine often turns each inner lookup into a B-tree probe, which is closer to O(N log M) and usually much cheaper in practice. On cached data, that may be only a handful of memory accesses per lookup; on cold storage, random I/O is what hurts.
NULL does not equal NULL under =, so rows with null join keys usually do not match.Memory rule: if you can picture one person walking down a list and asking, “Do you match?” for every row, you understand nested loop join.
Real-world story: In an e-commerce checkout service, a query may join a small set of cart items to an indexed product table and an indexed pricing table. When the cart has 5 to 20 rows, a nested loop join is often the best choice because each lookup is cheap and predictable. The database can walk the few cart rows and quickly fetch the matching product and price data.
What goes wrong is usually an indexing or join-order mistake. Suppose a deployment drops the index on product_id, or a query rewrite accidentally makes a 50,000-row enrichment job drive the join. Suddenly the database starts scanning millions of product rows for every batch record. Symptoms look like this: CPU jumps to 100%, the slow-query log shows execution time rising from milliseconds to tens of seconds, and application logs fill with timeout errors. Users feel it as a checkout spinner, retries, and abandoned carts.
The key lesson is that nested loop joins are not inherently bad; they are excellent when the data shape matches the algorithm. The outage happens when the assumption breaks and the inner lookup stops being cheap.
-- Demonstration: a small join where a nested loop strategy would be a natural fit.
-- The SQL below is standard and runnable in systems that support VALUES in CTEs.
-- We use tiny tables so the join logic is easy to see.
WITH customers(customer_id, customer_name) AS (
VALUES
(1, 'Ada'),
(2, 'Ben'),
(3, 'Cara')
),
orders(order_id, customer_id, amount) AS (
VALUES
(101, 1, 50.00),
(102, 1, 75.00),
(103, 4, 20.00), -- No matching customer: the inner side has nothing to return.
(104, NULL, 99.00) -- NULL does not match with '=' in the join predicate.
)
SELECT
c.customer_id,
c.customer_name,
o.order_id,
o.amount
FROM customers AS c
JOIN orders AS o
ON o.customer_id = c.customer_id
ORDER BY c.customer_id, o.order_id;
-- LEFT JOIN keeps every order, even when no customer matches.
-- This is where nested loop join must preserve the outer row and fill inner columns with NULLs.
WITH customers(customer_id, customer_name) AS (
VALUES
(1, 'Ada'),
(2, 'Ben'),
(3, 'Cara')
),
orders(order_id, customer_id, amount) AS (
VALUES
(101, 1, 50.00),
(102, 1, 75.00),
(103, 4, 20.00),
(104, NULL, 99.00)
)
SELECT
o.order_id,
o.customer_id,
c.customer_name
FROM orders AS o
LEFT JOIN customers AS c
ON c.customer_id = o.customer_id
ORDER BY o.order_id;Follow-up & Tricky Questions:
EXISTS checks because the search can stop at the first match.NULL.NULL join keys match each other? No, not with =. Two NULLs are not considered equal in normal SQL comparisons, so you need special logic such as IS NOT DISTINCT FROM if you want null-safe equality.Common Mistakes:
NULL matches NULL. Correction: normal = comparisons do not match nulls.Memory Hook: One row walks into the shop, asks every shelf the same question, and stops early if it finds a labeled drawer.
Cheat Sheet:
O(N×M) without an index.EXISTS and selective lookups.Practice Tasks:
customers and orders and identify the outer and inner sides.LEFT JOIN and observe which rows survive when there is no match.