EXISTS asks a yes/no question and can stop as soon as one row qualifies. IN materialises the subquery result. For a large subquery with duplicates, EXISTS is usually the better plan.
SELECT c.name FROM customer c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
SELECT c.name FROM customer c
WHERE c.id IN (SELECT customer_id FROM orders);
-- SELECT 1 vs SELECT * inside EXISTS makes no difference at all - the
-- column list is never evaluated.
EXISTS : Hash Semi Join -> 31 ms
IN : Hash Semi Join -> 33 ms (planner rewrote it)
-- For simple cases modern planners produce the same semi-join. They diverge
-- when the subquery can return NULL - and NOT IN then breaks entirely, which
-- is the real reason to prefer NOT EXISTS.
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-08-25