Hook: Interviewers love this question because one tiny word change can turn a correct query into a wrong report, a slow query, or a huge row explosion.
Question: What is the difference between JOIN and UNION in SQL?
Answer: JOIN combines rows from two tables side by side using a matching condition, so the result has columns from both tables. UNION stacks the results of two separate SELECT queries on top of each other, so the result has the same columns but more rows. If I want to enrich data, I use JOIN; if I want to merge two similar lists, I use UNION.
Interview-Ready Answer: I use JOIN when I need to combine columns from related tables, like orders with customers, and I use UNION when I need to stack two result sets with the same shape, like active users from two regions. A key detail is that UNION removes duplicates by default, while UNION ALL keeps them and is usually faster. So, join is about matching rows; union is about appending result sets.
Detailed Explanation:
Think of SQL in two different modes:
JOIN says: find rows that relate to each other through a key or condition, then place their columns together in one output row.JOIN means INNER JOIN, which keeps only matching rows. Outer joins like LEFT JOIN keep unmatched rows too.UNION means UNION DISTINCT in standard SQL, so duplicates are removed unless you write UNION ALL.JOIN, the database reads rows from both sides and checks the ON condition. The optimizer chooses a join algorithm such as nested loop (check each row against another set), hash join (build a hash table for fast lookups), or merge join (walk two sorted inputs together).UNION ALL, the engine usually just concatenates the two streams. It does not need to compare rows for duplicates, so it can often start returning rows quickly.UNION, the engine must also remove duplicates from the combined output. It typically does that with a sort or a hash-based distinct step.UNION or UNION ALL, the ORDER BY belongs at the end of the whole statement. Sorting inside one branch does not guarantee final order.JOIN when you need extra columns from another table, such as customer name, department name, or product price.UNION when you have two queries that return the same kind of rows, such as active users from region A and active users from region B.UNION ALL when you want every row preserved, or when duplicates are acceptable and speed matters.JOIN to combine unrelated lists, and do not use UNION when you really need columns from two tables in the same row.| Feature | JOIN | UNION | UNION ALL |
|---|---|---|---|
| Purpose | Match rows | Stack rows | Stack rows |
| Input shape | Different tables okay | Same columns needed | Same columns needed |
| Output shape | More columns | More rows | More rows |
| Duplicates | Can multiply rows | Removed | Kept |
| Typical cost | Depends on join plan | Extra dedupe work | Cheapest |
The exact cost depends on the plan, but the mental model is useful:
JOIN on indexed keys is often close to linear for the matched rows, especially with hash or merge joins. A bad nested-loop join without indexes can behave like O(n*m), which gets expensive very fast.UNION ALL is usually near O(n + m) because it just appends rows.UNION needs duplicate removal, so it is usually more expensive than UNION ALL. With sorting, it can feel like O((n+m) log(n+m)); with hashing, the average case is closer to linear but uses memory.UNION can spill to disk if the dedupe step does not fit in memory. That can turn a query that should take seconds into one that takes minutes.JOIN does not guarantee one output row per input row. If one customer has 5 orders, joining customers to orders returns 5 rows for that customer.UNION requires the same number of columns in each branch, and the data types must be compatible. The column names in the final result usually come from the first SELECT.UNION compares the combined results for duplicates, not each branch separately.NULL handling matters: outer joins can produce NULL values for missing matches, but UNION simply treats rows as rows; it does not fill in missing columns.Memory rule: JOIN stitches columns together; UNION stacks rows into one taller pile. If you remember that picture, you will almost never confuse them in an interview.
Real-World Story: A retail analytics team had separate web_signups and mobile_signups tables, plus a customers table with loyalty tier. To build one daily report, they should have used UNION ALL for the two signup streams, then JOIN the merged result to customers for enrichment. A developer mixed that up and used JOIN between the signup tables on email, which silently dropped users who signed up in only one channel and duplicated users who appeared in both.
The symptom was ugly: the dashboard underreported new signups by about 20%, marketing thought a campaign failed, and the database spent extra time sorting and joining far more rows than expected. In the query logs, the team saw big row estimates and slow execution around the reporting job. The fix was simple: stack the two event sources with UNION ALL, dedupe later only if needed, and reserve JOIN for the customer enrichment step.
-- Join vs UNION: runnable example
-- This script shows:
-- 1) JOIN = combine columns from related rows
-- 2) UNION / UNION ALL = stack compatible result sets
-- 3) An edge case: a row with no matching department becomes NULL in the LEFT JOIN
CREATE TABLE employees (
employee_id INTEGER PRIMARY KEY,
employee_name VARCHAR(50) NOT NULL,
dept_id INTEGER
);
CREATE TABLE departments (
dept_id INTEGER PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL
);
INSERT INTO employees (employee_id, employee_name, dept_id) VALUES
(1, 'Ava', 10),
(2, 'Ben', 20),
(3, 'Cara', 20),
(4, 'Drew', 30); -- edge case: no matching department
INSERT INTO departments (dept_id, dept_name) VALUES
(10, 'Sales'),
(20, 'Engineering'),
(40, 'Support');
-- JOIN: enrich each employee with a department name.
-- LEFT JOIN keeps Drew, even though dept_id = 30 has no match.
SELECT e.employee_id, e.employee_name, d.dept_name
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.dept_id
ORDER BY e.employee_id;
-- UNION ALL: stack two queries that return the same number of columns.
-- This is faster than UNION when you do not need duplicate removal.
SELECT employee_name AS label, 'employee' AS kind
FROM employees
UNION ALL
SELECT dept_name AS label, 'department' AS kind
FROM departments
ORDER BY kind, label;
-- UNION: removes duplicate rows from the combined result.
SELECT 'Ava' AS name
UNION
SELECT 'Ava' AS name;
-- UNION ALL: keeps duplicates, which proves the difference clearly.
SELECT 'Ava' AS name
UNION ALL
SELECT 'Ava' AS name;
Follow-up & Tricky Questions:
INNER JOIN and LEFT JOIN? INNER JOIN keeps only matching rows, while LEFT JOIN keeps every row from the left table and fills missing right-side columns with NULL.UNION ALL instead of UNION? Use UNION ALL whenever duplicates are acceptable or you plan to dedupe later, because it avoids the extra distinct step and is usually faster.UNION queries with different column names? Yes. The column names can differ, but the number of columns and their types must be compatible; the final output names usually come from the first SELECT.ORDER BY inside each branch control the final UNION order? No. Only the final ORDER BY after the full UNION or UNION ALL controls the returned order.UNION sort rows? No. It removes duplicates, but without a final ORDER BY the output order is not guaranteed.JOIN remove duplicates automatically? No. A join can duplicate rows if the match is one-to-many or many-to-many.JOIN the same as CROSS JOIN? No. Plain JOIN usually means INNER JOIN. A missing ON clause, however, can accidentally behave like a cross join and explode the row count.Common Mistakes:
UNION when you actually need related columns in one row. Fix: Use JOIN to combine tables side by side.JOIN when you really want to append two similar lists. Fix: Use UNION ALL or UNION depending on whether duplicates should remain.UNION requires the same number of columns. Fix: Align the SELECT lists and cast types when needed.Memory Hook: JOIN stitches columns; UNION stacks rows. If you can picture a zipper versus a stack of trays, you can answer this under pressure.
Cheat Sheet:
JOIN = match rows and bring columns together.UNION = append result sets with the same shape.UNION removes duplicates by default; UNION ALL keeps them.JOIN can multiply rows if the relationship is one-to-many.UNION needs the same column count and compatible types.ORDER BY goes at the end, not in the middle.Practice Tasks:
LEFT JOIN that shows every customer, even customers with no orders.UNION ALL, then compare the row count to UNION.