What does WHERE status IN ('new','open') mean, and does repeating a value in the list change the result?
answer
- Rewrite it without the keyword IN
- Think of it as a chain of comparisons
- One truth value per row, not one row per value
- Ask what an empty list does
basics
~20 sIN against a value list is shorthand for a chain of equality tests joined by OR: status = 'new' OR status = 'open'. It is a per-row test, so duplicates and the order of values in the list change nothing about which rows or how many rows come back.
solid answer
~50 s`status IN ('new','open')` is defined as `status = 'new' OR status = 'open'` — a membership test evaluated once per row, returning TRUE, FALSE or UNKNOWN. Because it is a **predicate, not a join**, it can only keep or drop a row; it can never multiply one. So `IN ('new','new','open')` returns exactly the same rows as `IN ('new','open')`, and reordering the list changes nothing either. That is the key contrast with joining against a table of values, where a duplicate value on the other side does fan out the result. Two practical notes: an empty list, `IN ()`, is a syntax error rather than "matches nothing", so application code that builds the list must handle the empty case; and every element is compared to the column, so mixing types in the list drags implicit conversion into the comparison.
code
sql · 8 lines-- these two return exactly the same rows
SELECT * FROM tickets WHERE status IN ('new', 'new', 'open');
SELECT * FROM tickets WHERE status = 'new' OR status = 'open';
-- but a join to the same values fans out on the duplicate (PostgreSQL syntax)
SELECT t.* FROM tickets t
JOIN (VALUES ('new'), ('new'), ('open')) AS s(status) ON t.status = s.status;
-- every 'new' ticket now appears twicego deeper
Be able to expand an IN list into its OR chain on the spot and state that it filters rows one at a time, so list duplicates and ordering do not affect the result.
Explain the predicate-versus-join distinction in terms of row multiplicity, and know the practical edges: the empty-list syntax error and implicit conversion when the literals do not match the column type.
Show where the value set belongs — inline literals versus a table or a parameterised set — and how you keep a rewrite between IN and a join from silently changing row counts in a report.
Take a position on where membership sets live across a codebase: hard-coded lists scattered through queries drift apart over time, and centralising them as reference data is a maintainability call, not a syntax one.
## The definition The `IN` predicate with an explicit value list is defined by the standard as a disjunction of equality comparisons: ```sql WHERE status IN ('new', 'open', 'pending') -- means exactly: WHERE status = 'new' OR status = 'open' OR status = 'pending' ``` (The standard expresses this as an equivalence with the quantified comparison `= ANY (list)`, which is the same idea.) `IN` exists for readability: one column reference instead of three, and one place to edit when the list changes. ## It is a row filter, not a row multiplier This is the property interviewers probe. `IN` is evaluated once per row of the input and produces a single truth value, so each candidate row is either kept or dropped. Consequences: - **Duplicates in the list are irrelevant.** `status IN ('new','new','open')` keeps exactly the rows `status IN ('new','open')` keeps. A row matching `'new'` appears once, not twice. - **Order is irrelevant.** The list is a set of candidate values, not a sort key; it has no effect on the order of the result, which is undefined without `ORDER BY` anyway. - **Cardinality is bounded by the input.** The result can never have more rows than the table being filtered. Compare that with joining to a list of values: ```sql SELECT o.* FROM orders o JOIN (VALUES ('new'), ('new'), ('open')) AS s(status) ON o.status = s.status; -- PostgreSQL syntax ``` Here the duplicate `'new'` on the right side is a second matching row, so every `'new'` order comes back **twice**. Same intent, different row multiplicity. If you ever rewrite an `IN` list into a join, deduplicate the value set or use a semi-join form so the multiplicity is preserved. ## Empty lists `WHERE id IN ()` is a **syntax error** in standard SQL and in the major engines — not a predicate that matches nothing. Any code that assembles the list from a collection must branch on the empty case: either skip the predicate entirely (if "no filter" is the intent) or emit a predicate that is deliberately false, such as `WHERE 1 = 0`. This is one of the most common runtime failures in query-building code, because it only fires when the caller's list happens to come back empty. ## Types in the list Every element is compared against the column, so the column's type governs. `WHERE user_id IN ('1','2')` on an integer column forces an implicit conversion of each literal, and `WHERE code IN (1, 'A')` on a text column may error or convert depending on the engine. Keep the list homogeneous and of the column's type; it removes an entire class of surprises and keeps the comparison honest. ## NOT IN over a value list `x NOT IN (a, b)` negates the disjunction and so expands to `x <> a AND x <> b` — note the `AND`. Every element must be unequal for the row to qualify. Take care that a list which can carry a NULL element changes the picture, because a comparison with NULL yields UNKNOWN rather than TRUE. ## Readability and maintenance A short literal list is fine and is idiomatic — status codes, country codes, a handful of ids from a fixture. When the list is long, or the same list appears in several queries, the value set is really data: putting it in a table (or passing it as a parameterised set) keeps the queries stable while the membership changes. As a rule of thumb, if you would not want to read the list aloud, it does not belong inline in the statement. ## What to say Give the OR expansion first, then the multiplicity point — `IN` filters, it never duplicates — because that is what distinguishes a candidate who understands it from one who has only used it. Add the empty-list syntax error if you have written query-building code, because it is the failure people actually hit in production.
- Rewriting an IN list as a join to a VALUES list changed the row count. Why?A join multiplies rows: if the value set contains the same value twice, every matching row on the left pairs with both copies and appears twice. `IN` is a predicate and can only keep or drop a row. Deduplicate the value set, or express the rewrite as a semi-join so multiplicity is preserved.
- Your code builds the IN list from a collection that can be empty. What happens, and what do you do?`IN ()` is a syntax error, not an empty match — the statement fails at parse time. Handle it in the builder: if the collection is empty, either omit the predicate (when "no filter" is meant) or substitute an always-false predicate such as `1 = 0` so the query returns no rows deliberately.
- How does NOT IN against a literal value list expand?`x NOT IN (a, b, c)` is the negation of the OR chain, so it becomes `x <> a AND x <> b AND x <> c` — every comparison must hold. Writing it by hand with OR instead of AND is a common bug that lets rows through. Be careful if the list can contain a NULL element, since comparing to NULL gives UNKNOWN rather than TRUE.
saying these in an interview costs you the question
- Thinks a value repeated in the IN list duplicates matching rows
- Expects IN () to match nothing instead of failing to parse
- Expands NOT IN with OR instead of AND
- Believes the list order affects the order of the result
- Mixes literal types and assumes every engine converts identically