Backend & Node.js · APIs, data and failure
How do SQL joins work and how do you choose the right one?
Short answer
An inner join returns only matching rows from both tables. A left join keeps all left rows and fills unmatched right columns with null. Right and full joins mirror that, and a cross join produces every combination.
A join combines rows from two sources according to a predicate. The database may use nested loops, a hash join or a merge join depending on size, indexes and statistics. The logical result is the same; the performance is not.
SELECT u.id, u.email, o.id AS order_id, o.total_cents
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at >= '2026-01-01'
ORDER BY u.id, o.id;Types and traps
- INNER JOIN keeps only rows satisfying the condition
- LEFT JOIN keeps all left rows, even without a match
- RIGHT JOIN keeps all right rows; it is often rewritten as a left join for readability
- FULL OUTER JOIN keeps unmatched rows from both sides
- CROSS JOIN has no predicate and can explode row counts
- A condition in WHERE on the right table can turn a left join into an inner join
- Indexes on join keys matter, especially for nested-loop plans
A frequent bug is putting a filter for the optional side in WHERE instead of the ON clause. For example, WHERE o.status = 'paid' removes users with no orders, because null does not satisfy the comparison. If the intent is to keep all users and only attach paid orders, put the condition in ON.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1How does a left join differ from an inner join?
- 2Why can a WHERE filter change a left join into an inner join?
- 3When should you use EXISTS instead of a join?
- 4How does the database choose a join algorithm?
Say it in the interview
An inner join returns only matches, while a left join keeps every row from the left side and fills missing right columns with null. I choose based on whether the related row is required or optional. A common mistake is filtering the optional side in WHERE, which silently removes unmatched rows; that condition belongs in ON. I also check indexes on join keys and avoid joins that multiply rows before aggregation.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Backend & Node.js mock interview and get scored feedback on depth, clarity and confidence.