Hook: Interviewers love this one because it checks whether you know which side of a join is being preserved, not just whether you can type the syntax.
Question: What is a RIGHT JOIN?
Answer: A RIGHT JOIN returns every row from the table on the right side of the join. When a row on the right has no match on the left, the left-side columns come back as NULL, which means “missing value.” It is basically the mirror image of a LEFT JOIN.
Interview-Ready Answer: I think of a RIGHT JOIN as “keep all rows from the right table, and bring in matches from the left when they exist.” If there is no match, the left columns are filled with NULL. In practice, I often rewrite it as a LEFT JOIN by swapping the table order, because that is usually easier to read and reason about. One important detail is that filters on the preserved side should usually stay in the ON clause if you want to keep the unmatched rows.
A RIGHT JOIN is an outer join, which means it does not throw away unmatched rows from one side. In this case, the right table is the preserved side. That preserved side is the key idea: every row from that table survives, even if the other table has no match.
ON.NULL.WHERE, GROUP BY, HAVING, and ORDER BY in that order. This is why a WHERE filter can accidentally remove the unmatched rows that the RIGHT JOIN just preserved.That last step is a classic interview trap. SQL uses three-valued logic, meaning a condition can be TRUE, FALSE, or UNKNOWN. Comparisons with NULL usually become UNKNOWN, and WHERE keeps only TRUE, so unmatched rows often disappear if you filter on the non-preserved side after the join.
| Join type | Rows kept | Unmatched rows | Typical use |
|---|---|---|---|
| INNER | Only matches | Discarded | Find overlaps |
| LEFT | All left rows | Right becomes NULL | Most common outer join |
| RIGHT | All right rows | Left becomes NULL | Mirror of LEFT |
| FULL | All rows both sides | Both sides can be NULL | Complete comparison |
In pure logic, a RIGHT JOIN is not special; it is just a LEFT JOIN written from the other side. That is why many teams prefer LEFT JOIN for readability. The optimizer may even rewrite one into the other internally if it can prove the result is the same.
There is no fixed time complexity for a RIGHT JOIN because the optimizer chooses the plan. With a hash join, the work is often close to O(n + m) on average, with memory roughly proportional to the smaller input. With nested loops, worst-case work is O(n * m), which becomes huge fast: 1,000,000 rows by 1,000,000 rows is far too many comparisons. In practice, indexes on join keys and good statistics matter much more than whether the keyword is RIGHT or LEFT.
NULL does not equal NULL: a normal equality join will not match null keys.WHERE can cancel the outer join: a filter on left-side columns after a RIGHT JOIN can remove the preserved right rows.ORDER BY.Memory trick: think “the table on the right gets the VIP pass.” Everyone on that side gets in; the left side only joins if it has a matching ticket.
Imagine a subscription billing service that needs a daily reconciliation report. The report must show every invoice that was generated today, even if the customer profile is missing or late to sync from another system. The invoice table is the right table, so the report uses a RIGHT JOIN to keep all invoices and attach customer details when available.
What goes wrong when someone misunderstands it? A developer adds WHERE customer.status = 'active' after the RIGHT JOIN. That looks harmless, but it removes invoices whose customer row is missing, because the customer columns are NULL for unmatched rows. The finance dashboard suddenly undercounts billed revenue. The symptom is a row-count mismatch: the invoice table says 120,000 rows for the day, but the report only shows 117,842. Logs look normal, yet support tickets start saying “missing invoices” and “payment totals do not match.” The real bug is not the join itself; it is filtering after the join in a way that destroys the preserved rows.
-- RIGHT JOIN demo using standard SQL VALUES tables.
-- The right table is the one we keep completely.
WITH customers(customer_id, customer_name) AS (
VALUES
(1, 'Ava'),
(2, 'Ben'),
(3, 'Cara')
),
orders(order_id, customer_id, amount) AS (
VALUES
(101, 1, 50),
(102, 1, 75),
(103, 4, 20) -- No matching customer: this is the edge case we want to preserve
)
SELECT
o.order_id,
o.customer_id AS order_customer_id,
o.amount,
c.customer_id AS matched_customer_id,
c.customer_name
FROM customers c
RIGHT JOIN orders o
ON c.customer_id = o.customer_id
ORDER BY o.order_id;
-- Equivalent rewrite using LEFT JOIN.
-- Many teams prefer this version because it reads left-to-right:
-- keep all orders, then attach customers when possible.
WITH customers(customer_id, customer_name) AS (
VALUES
(1, 'Ava'),
(2, 'Ben'),
(3, 'Cara')
),
orders(order_id, customer_id, amount) AS (
VALUES
(101, 1, 50),
(102, 1, 75),
(103, 4, 20)
)
SELECT
o.order_id,
o.customer_id AS order_customer_id,
o.amount,
c.customer_id AS matched_customer_id,
c.customer_name
FROM orders o
LEFT JOIN customers c
ON c.customer_id = o.customer_id
ORDER BY o.order_id;
-- Edge case: a WHERE filter on the preserved side can remove the NULL-extended row.
-- This keeps only Ava's orders and drops the unmatched order 103, because c.customer_name
-- is NULL for that row and WHERE runs after the join.
WITH customers(customer_id, customer_name) AS (
VALUES
(1, 'Ava'),
(2, 'Ben'),
(3, 'Cara')
),
orders(order_id, customer_id, amount) AS (
VALUES
(101, 1, 50),
(102, 1, 75),
(103, 4, 20)
)
SELECT
o.order_id,
o.customer_id AS order_customer_id,
o.amount,
c.customer_name
FROM customers c
RIGHT JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.customer_name = 'Ava'
ORDER BY o.order_id;Follow-up & Tricky Questions:
NULL. That is the defining feature of an outer join.ON and WHERE? ON decides which rows match during the join, while WHERE filters the final rows afterward. A filter in WHERE can remove the null-extended rows and accidentally make the result behave like an inner join.ORDER BY.NULL? A normal equality join will not match NULL to NULL. If you need null-safe matching, use a database-specific null-safe operator or a condition built for that purpose.WHERE left_table.id IS NOT NULL after a RIGHT JOIN, what happens? You remove the null-extended rows, so the query behaves much more like an inner join. That is a common bug because the JOIN looked outer, but the WHERE clause erased the outer part.Common Mistakes:
WHERE. Fix: Move the condition into ON if you want to keep unmatched rows.NULL does not match NULL with =. Fix: Use null-safe logic when that behavior is needed.Memory Hook: “Right side gets the spotlight.” The right table is always kept; the left table only joins when it can follow along.
Cheat Sheet:
NULL.ON matches rows; WHERE filters after the join.Practice Tasks:
ON and once in WHERE, and compare the result.