Why can SELECT * FROM orders LIMIT 10 return different rows each time it runs?
answer
- nothing in SQL promises a row order
- the clause that would fix it is missing
- slicing an undefined order is undefined
- a duplicate sort key leaves the same hole
basics
~20 sWithout ORDER BY, a SQL result has no defined order, so LIMIT 10 returns whichever ten rows the engine produced first. A different plan, different data or different timing can produce a different ten. Only a total ORDER BY makes the slice deterministic.
solid answer
~50 sA SQL result is a bag of rows with no inherent order; `ORDER BY` is the only thing that imposes one. So `LIMIT 10` with no `ORDER BY` asks for "any ten" — the engine is free to return whatever its access path emitted first, and that can change when statistics change, when an index is added, when rows are updated, or when the same query runs on a different node. The query is not wrong, it is *unspecified*, and it often looks stable in development because the plan never changes there. Adding `ORDER BY` is necessary but not sufficient: if the sort key has duplicates, rows tied on that key may come back in any order, so the boundary between page 3 and page 4 still wobbles. A deterministic slice needs a **total** ordering — append a unique column such as the primary key: `ORDER BY created_at DESC, id DESC`.
go deeper
Remember the rule as a habit: any query with LIMIT needs an ORDER BY. Without one, the ten rows you get are whatever the database felt like producing first.
Explain the mechanism — a result is an unordered multiset, ORDER BY is the only ordering guarantee, and a non-unique sort key leaves ties undefined. Name the fix: append a unique tie-breaker column.
Be ready to diagnose the field report — users seeing a row twice, a flaky test, a top-N list that shuffles — and trace it back to a plan change under an unspecified ordering rather than to data corruption.
Own it as a standard: list endpoints declare a total ordering, and reviews reject a LIMIT whose sort key is not provably unique. Unspecified behaviour that currently works is technical debt with a trigger you do not control.
## Results are unordered unless you order them SQL defines a query result as a multiset of rows. Nothing in the language says rows come back in insertion order, primary-key order, or physical storage order. `ORDER BY` is the single construct that imposes an order on the rows a query returns, and it is the only guarantee you get. Most of the time that formal fact is harmless, because the client reads the whole result and does its own thing with it. `LIMIT` is what turns the formality into a bug: once you keep only part of the result, an unspecified *order* becomes an unspecified *set of rows*. "Any order" and "any ten rows" are very different problems. ```sql -- unspecified: which ten rows is entirely up to the engine SELECT * FROM orders LIMIT 10; -- specified: the ten oldest, ties broken by id SELECT * FROM orders ORDER BY created_at, id LIMIT 10; ``` ## Why the answer changes over time The rows an unordered query emits first are an artefact of how it was executed, and that can change without anyone touching the SQL: a new index gives a different access path, a rewrite or updated statistics change the chosen plan, rows are updated and relocated, parallel execution interleaves producers differently, or the query runs against a replica whose physical layout differs. The dangerous property is that none of these show up in testing. A small development table with no concurrent writes will hand back the same ten rows all day, so the defect ships and surfaces later as "the dashboard shows different top customers on refresh". ## Ordering that is not total is still non-deterministic Adding any `ORDER BY` is not automatically enough. Consider a table where thousands of rows share a `created_on` date: ```sql SELECT id FROM events ORDER BY created_on LIMIT 10 OFFSET 10; ``` All rows with the same `created_on` are **peers** as far as the sort is concerned, and SQL says nothing about the relative order of peers. Two consecutive requests can legitimately order the tied block differently, so a row can appear on page 2 and again on page 3, while another appears on neither. This is the same class of bug as having no `ORDER BY` at all, just narrowed to the ties. The fix is to make the ordering **total** — no two rows can compare equal on the full key list. In practice: append a column that is unique within the table, usually the primary key, and match its direction to the leading key so the intent stays readable. ```sql SELECT id FROM events ORDER BY created_on DESC, id DESC LIMIT 10 OFFSET 10; ``` Now every row has exactly one position in the ordering, and the slice is reproducible for a given data set. ## Where this bites in practice - **Paging.** Page boundaries land in the middle of a tied block, so users see duplicates and gaps between pages. - **"Give me a sample."** `LIMIT 100` with no order is often used as a sample; it is not a random sample, and it is not a stable one either. If reproducibility matters, order by a key. - **Top-N reporting.** `LIMIT 5` without `ORDER BY` on the measure is not "the top five", it is five arbitrary rows. - **Tests.** A test that asserts on the first row of an unordered limited query is flaky by construction; it will pass until an index is added. ## Sanity checks before you ship a limited query 1. Is there an `ORDER BY` at all? If not, can you honestly say "any rows will do"? If not, add one. 2. Can two different rows compare equal on the full sort key? If yes, append a unique tie-breaker. 3. Does the caller need the *same* rows across separate requests? Determinism per statement is not the same as page stability while other sessions insert and delete — that is a separate property of offset-based paging, and needs a separate answer. The short version an interviewer wants to hear: `LIMIT` slices an order, it does not create one, and the slice is only reproducible when the order is total.
- The query already has ORDER BY created_at — is the LIMIT slice now deterministic?Only if `created_at` is unique. Rows sharing the same value are peers, and SQL leaves the order among peers undefined, so the page boundary can fall differently between runs. Append a unique tie-breaker — `ORDER BY created_at, id` — to make the ordering total and the slice reproducible.
- The same LIMIT query has returned the same rows for months. Is it actually safe?No — it is unspecified behaviour that happens to be stable. The rows come from whatever access path the planner chose; adding an index, refreshing statistics, updating rows or running on a replica can all change it with no code change. Stability observed is not stability guaranteed.
- Is ORDER BY without LIMIT harmless if nobody cares about order?Semantically yes, but it makes the engine sort a result the caller does not need, which is wasted work on large results. The point is the reverse: never omit ORDER BY when you *do* slice. Order because the result's order is part of the contract, not by reflex.
saying these in an interview costs you the question
- Claims rows come back in insertion or primary-key order
- Says the physical table order determines the result
- Thinks any ORDER BY makes a page deterministic
- Treats months of stable output as a guarantee
- Suggests DISTINCT or an index to stabilise the page