Question: What is an INNER JOIN in SQL?
Answer: An INNER JOIN returns only the rows that match in both tables. If a row in one table has no matching row in the other table, it is dropped from the result. Think of it as the overlap between two lists.
Interview-Ready Answer: I use an INNER JOIN when I want only the rows where the join condition matches on both sides. So if a customer has no orders, that customer will not appear in the result. The key idea is that it keeps the intersection, not the leftovers, and the output can still grow if one row matches many rows on the other side.
Detailed Explanation: An INNER JOIN combines rows from two tables when the ON condition is true. In plain words: SQL looks for pairs that belong together, builds a combined row, and throws away every non-matching row. The important mental model is that a join is not magic table merging; it is row matching.
customer_id.Under the hood, the database optimizer chooses an execution plan. Common strategies are nested loop join (check many pairs one by one), hash join (build a lookup structure for one side), and merge join (walk through sorted inputs together). You do not usually choose the algorithm directly in SQL, but your indexes, filters, and data sizes influence which one the optimizer picks.
For interview purposes, the key performance idea is simple: a good join on indexed keys can be fast, while a join with no useful index on a huge table can become expensive. In a naive nested loop, cost can look like O(n*m), but a hash join often behaves closer to O(n+m) for the join step, assuming enough memory. Real databases may spill to disk if the hash table is too large, so very large joins can get much slower.
Use an INNER JOIN when you only care about records that exist in both places: paid orders with payments, employees with departments, products with categories, or users with active subscriptions. It is the default choice when the business question is about matching data, not missing data.
| Join type | What it keeps | Typical use |
|---|---|---|
| INNER JOIN | Only matches | Intersection |
| LEFT JOIN | All left rows | Find missing right rows |
| RIGHT JOIN | All right rows | Mirror of left join |
| CROSS JOIN | Every pair | Combinations |
Memory hook: picture two guest lists at a party. An INNER JOIN only lets in people whose name appears on both lists.
Duplicates multiply rows: if one customer has 3 orders, that customer appears 3 times after the join. If both sides have repeated keys, the result can grow quickly. That is not a bug; it is how row matching works.
NULL does not equal NULL: in normal SQL comparisons, NULL means unknown, so NULL = NULL is not true. That means rows with null join keys usually do not match unless you use a dialect-specific null-safe comparison.
Filtering matters: putting conditions in the ON clause changes which rows count as matches. With an INNER JOIN, a condition in WHERE often gives the same final rows as putting it in ON, but not always when outer joins are involved, so be precise from the start.
Logical vs physical order: logically, the join matches rows first and then returns the selected columns. Physically, the optimizer may reorder operations for speed, but the result must still obey SQL rules.
Real-World Example: Imagine a checkout service for an online store. A product analyst wants a report of only paid orders, so the team joins orders to payments using order_id. An INNER JOIN is perfect here because unpaid orders should not appear in the paid-orders report.
What goes wrong when someone misunderstands it? A junior engineer expects the query to show every order and uses an inner join anyway. Suddenly, canceled orders and orders with missing payment records disappear from the dashboard. The symptom is a report that looks too low, support sees customers asking where their purchases went, and logs show fewer rows than expected even though the source tables still contain the data.
The lesson: if the business question is show me the matches, inner join is correct. If the question is show me everything and mark missing matches, you need a left join instead.
-- Example: INNER JOIN keeps only matching rows from both tables.
-- This script is self-contained and runnable in many SQL databases.
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
customer_name VARCHAR(50) NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER,
amount DECIMAL(10,2) NOT NULL
);
INSERT INTO customers (customer_id, customer_name) VALUES
(1, 'Ava'),
(2, 'Ben'),
(3, 'Chloe');
INSERT INTO orders (order_id, customer_id, amount) VALUES
(100, 1, 25.00),
(101, 1, 40.00),
(102, 2, 15.50),
(103, NULL, 99.99); -- Edge case: NULL customer_id will not match any customer
-- INNER JOIN returns only the matching customer/order pairs.
SELECT
c.customer_id,
c.customer_name,
o.order_id,
o.amount
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.customer_id
ORDER BY c.customer_id, o.order_id;
-- Failure path demo: Chloe has no orders, so this query returns zero rows.
-- That is expected for INNER JOIN; unmatched left-side rows are dropped.
SELECT
c.customer_name,
o.order_id
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.customer_id
WHERE c.customer_id = 3;
INNER JOIN and LEFT JOIN?NULL. If you want to find missing relationships, left join is the better tool.NULL?NULL to anything, including another NULL, because NULL means unknown. That is a very common source of missing rows.INNER JOIN remove duplicates?DISTINCT or a different query shape.ON clause an inner join?Tricky / gotcha questions:
c.customer_id, to avoid ambiguity and to make your query readable.ON to WHERE, is it always the same for inner joins?Common Mistakes:
INNER JOIN when you actually need missing rows too. Correction: switch to LEFT JOIN if you need every left-side row.NULL values will match. Correction: remember that NULL is unknown, so equality usually fails.Memory Hook: Think of an inner join as the intersection of two guest lists: only people invited on both lists get in.
Cheat Sheet:
ON.NULL does not match with =.Practice Tasks:
employees to departments and return only employees who have a department.