SQL Practice

SQL ORDER BY Explained

You're going to underestimate ORDER BY. Almost everyone does. It looks like a cosmetic clause, and then an interviewer asks why your pagination returns the same row twice across pages. The answer is that without a tiebreaker column, your sort isn't deterministic, and that's the moment the question gets interesting.

In our verified corpus ORDER BY appears in 17% of SQL rounds on its own, and in another 21% through the OVER clause of window functions. Taken together you'll use ORDER BY on more than a third of the questions you see. Slow down on this page. It's the clause that separates candidates who understand result sets from candidates who just type them.

17%

ORDER BY alone

21%

Inside window OVER clauses

1 in 3

Questions you'll touch it

5

Patterns worth knowing cold

Source: DataDriven analysis of 1,042 verified data engineering interview rounds.

ORDER BY Syntax

ORDER BY runs late in the pipeline. It shows up after WHERE, GROUP BY, and HAVING, right before LIMIT. Because it runs after SELECT, you get to reference column aliases you defined in the projection. That's a freebie you should use, and it's one of the things interviewers watch you for. ORDER BY can reference column aliases defined in SELECT, which is not possible in WHERE or GROUP BY in standard SQL.

-- Full clause order
SELECT columns
FROM table
WHERE condition
GROUP BY columns
HAVING aggregate_condition
ORDER BY sort_expression [ASC|DESC] [NULLS FIRST|NULLS LAST]
LIMIT n OFFSET m;

-- ORDER BY with alias (works because ORDER BY executes after SELECT)
SELECT
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;

Execution order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. ORDER BY runs after SELECT, which is why it can use aliases. WHERE runs before SELECT, which is why it cannot.

5 ORDER BY Patterns

From basic ASC/DESC to custom CASE ordering. These patterns cover the full range of ORDER BY usage in interviews and production queries.

ASC and DESC

ASC sorts from smallest to largest (alphabetical A-Z, earliest to latest, lowest to highest). DESC reverses the order. ASC is the default when no direction is specified. In interviews, always state the sort direction explicitly. Omitting it forces the reader to remember the default, which adds cognitive load to code review.

-- Explicit sort direction (always recommended)
SELECT employee_name, salary
FROM employees
ORDER BY salary DESC;

-- ASC is the default, but be explicit
SELECT employee_name, hire_date
FROM employees
ORDER BY hire_date ASC;

Multiple Columns

ORDER BY accepts multiple columns separated by commas. The first column is the primary sort. Ties in the primary sort are broken by the second column. Ties in the second are broken by the third, and so on. Each column can have its own ASC or DESC direction. This is essential for deterministic ordering in pagination, deduplication, and reporting queries.

-- Sort by department (A-Z), then salary (highest first) within department
SELECT department, employee_name, salary
FROM employees
ORDER BY department ASC, salary DESC;

-- Three-level sort for pagination
SELECT *
FROM orders
ORDER BY order_date DESC, customer_id ASC, order_id ASC
LIMIT 50 OFFSET 100;

NULLS FIRST and NULLS LAST

NULLs have no inherent sort position. Different engines handle them differently by default. PostgreSQL sorts NULLs last for ASC and first for DESC. SQL Server sorts NULLs first for ASC. This inconsistency is a trap in interviews and cross-engine migrations. Use NULLS FIRST or NULLS LAST explicitly to control placement regardless of engine defaults.

-- PostgreSQL: explicit NULL placement
SELECT employee_name, bonus
FROM employees
ORDER BY bonus DESC NULLS LAST;

-- Employees without bonuses sort to the bottom
-- Without NULLS LAST, PostgreSQL puts NULLs first for DESC

-- SQL Server workaround (no NULLS FIRST/LAST syntax)
SELECT employee_name, bonus
FROM employees
ORDER BY CASE WHEN bonus IS NULL THEN 1 ELSE 0 END, bonus DESC;

ORDER BY Expression

ORDER BY can sort by computed expressions, not just column names. You can sort by string length, date extraction, arithmetic, or any valid expression. The expression does not need to appear in the SELECT list (except in some older MySQL versions with DISTINCT). This gives you flexible sorting without cluttering the output columns.

-- Sort by year, then by amount within each year
SELECT order_id, order_date, amount
FROM orders
ORDER BY EXTRACT(YEAR FROM order_date) DESC, amount DESC;

-- Sort by string length
SELECT product_name
FROM products
ORDER BY LENGTH(product_name) ASC;

-- Sort by column position (less readable, but valid)
SELECT department, employee_name, salary
FROM employees
ORDER BY 1 ASC, 3 DESC;  -- 1 = department, 3 = salary

ORDER BY with CASE

CASE inside ORDER BY creates custom sort orders that do not follow alphabetical or numeric sequence. This is useful when business logic dictates a specific ordering: statuses (pending before active before closed), priority levels, or categorical rankings. The CASE expression assigns a numeric sort key to each category.

-- Custom status ordering
SELECT order_id, status, order_date
FROM orders
ORDER BY
  CASE status
    WHEN 'pending' THEN 1
    WHEN 'processing' THEN 2
    WHEN 'shipped' THEN 3
    WHEN 'delivered' THEN 4
    WHEN 'cancelled' THEN 5
  END ASC,
  order_date DESC;

-- Priority-based sorting with fallback
SELECT ticket_id, priority, created_at
FROM support_tickets
ORDER BY
  CASE priority
    WHEN 'critical' THEN 1
    WHEN 'high' THEN 2
    WHEN 'medium' THEN 3
    WHEN 'low' THEN 4
    ELSE 5
  END ASC,
  created_at ASC;

ORDER BY in Window Functions

ORDER BY appears in two different contexts. At the query level, it sorts the final output. Inside a window function's OVER clause, it controls the ranking sequence, the running total direction, and the default frame boundary. These are independent. The query-level ORDER BY does not affect window function behavior, and the window ORDER BY does not affect output row order.

-- Two different ORDER BYs, two different purposes
SELECT
  department,
  employee_name,
  salary,
  RANK() OVER (
    PARTITION BY department
    ORDER BY salary DESC          -- controls ranking within the window
  ) AS dept_rank
FROM employees
ORDER BY department ASC, salary DESC;  -- controls output row order

-- Without the query-level ORDER BY, the output order is undefined
-- even though the window has its own ORDER BY

Interview note: A common mistake is assuming that ORDER BY inside OVER sorts the final output. It does not. You need a separate ORDER BY at the end of the query for output ordering. The window ORDER BY only determines how the window function processes rows within each partition.

ORDER BY Performance

Sorting is one of the most expensive operations in SQL. The engine must hold the entire result set (or spill to disk) and sort it before returning rows. Here is what affects ORDER BY performance and how to optimize it.

Indexes eliminate sorting

If an index exists on the ORDER BY columns in the correct direction, the engine reads rows in sorted order directly from the index. No sort step needed. This is the primary optimization. For queries with WHERE + ORDER BY + LIMIT, a composite index covering the filter and sort columns can turn a full-table scan into an index range scan.

LIMIT reduces sort cost

ORDER BY with LIMIT N allows the engine to use a top-N sort, which only tracks the top N rows instead of sorting the entire result set. This is much faster for large tables when you only need the first page of results.

Avoid ORDER BY on large intermediate results

In CTEs and subqueries, ORDER BY is usually ignored by the optimizer (the outer query determines final order). Adding ORDER BY to a CTE wastes parse time and can confuse readers into thinking the CTE output is ordered. Only use ORDER BY in the outermost query or when combined with LIMIT in a subquery.

4 ORDER BY Interview Questions

These questions test sorting mechanics, NULL behavior, and real-world pagination bugs that you will encounter in production systems.

Q1: Why does the same query return rows in different order on different runs? How do you fix it?

What they test:

Understanding of nondeterministic ordering. Without ORDER BY, SQL makes no guarantee about row order. The engine is free to return rows in any sequence, and that sequence can change between executions due to parallel scans, cache state, or plan changes. Even with ORDER BY, if the sort columns have ties, the tied rows can appear in any order. The fix is a deterministic ORDER BY with a unique tiebreaker.

Approach:

Explain that SQL result sets are unordered unless ORDER BY is specified. For deterministic ordering, the ORDER BY columns must form a unique combination. Add a primary key or unique column as the last sort key: ORDER BY created_at DESC, id ASC. This guarantees the same order every time.

Q2: How do NULLs sort in ORDER BY? Does it differ between PostgreSQL and SQL Server?

What they test:

Cross-engine awareness. PostgreSQL: NULLs sort last for ASC, first for DESC. SQL Server: NULLs sort first for ASC. MySQL: NULLs sort first for ASC (treated as the lowest value). The interviewer wants to know if you have worked across engines and whether you use NULLS FIRST/LAST to make behavior explicit.

Approach:

State the defaults for at least two engines. Then recommend always using NULLS FIRST or NULLS LAST explicitly. For SQL Server, which lacks that syntax, show the CASE WHEN IS NULL workaround. Mention that window function ORDER BY clauses also follow the same NULL sorting rules.

Q3: Can you ORDER BY a column that is not in the SELECT list? What about with DISTINCT?

What they test:

SQL specification knowledge. Standard SQL allows ORDER BY on columns not in SELECT. However, SELECT DISTINCT restricts ORDER BY to columns in the SELECT list (because the engine cannot resolve which row's non-selected column to use for ordering after dedup). PostgreSQL enforces this. MySQL is sometimes lenient. The interviewer checks if you know this edge case.

Approach:

Without DISTINCT: yes, you can ORDER BY any column from the source tables. With DISTINCT: you can only ORDER BY columns that appear in the SELECT list. This is because DISTINCT collapses rows, and the non-selected column might have different values across collapsed rows, making the sort ambiguous.

Q4: A paginated query uses OFFSET and LIMIT with ORDER BY created_at DESC. Users report seeing duplicate rows across pages. What is wrong?

What they test:

A classic production bug. If created_at has duplicate values, the sort is nondeterministic within those ties. Between page requests, the engine might order tied rows differently, causing some to appear on two pages and others to disappear. The fix is to add a unique tiebreaker to the ORDER BY.

Approach:

Add a unique column to the sort: ORDER BY created_at DESC, id ASC. This makes the ordering fully deterministic. Every row has a unique position in the sorted output, so OFFSET + LIMIT produces stable pages. For high-traffic systems, consider keyset pagination (WHERE created_at < ?) instead of OFFSET, which avoids the skip-scan performance penalty.

ORDER BY FAQ

What does ORDER BY do in SQL?+
ORDER BY sorts the result set by one or more columns or expressions. Without ORDER BY, SQL does not guarantee any particular row order. ASC sorts ascending (smallest to largest, A to Z, earliest to latest). DESC sorts descending. ASC is the default when no direction is specified. ORDER BY is the last clause executed before LIMIT, so it sees the full filtered and grouped result set.
Can I ORDER BY multiple columns?+
Yes. ORDER BY column1 ASC, column2 DESC sorts primarily by column1 in ascending order, and within ties on column1, sorts by column2 in descending order. Each column can have its own ASC or DESC direction. There is no practical limit to the number of sort columns, though more columns mean more work for the engine.
How do NULLs sort in ORDER BY?+
It depends on the engine. PostgreSQL puts NULLs last for ASC and first for DESC by default. SQL Server puts NULLs first for ASC. Use NULLS FIRST or NULLS LAST (PostgreSQL, Oracle, Snowflake) to override the default. SQL Server and MySQL do not support NULLS FIRST/LAST syntax, but you can use a CASE expression as a workaround: ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col.
Does ORDER BY affect performance?+
Yes. ORDER BY requires the engine to sort the result set, which costs O(N log N) time and potentially significant memory. If the result exceeds available memory, the sort spills to disk, which is much slower. An index on the ORDER BY columns can avoid the sort entirely if the query can read rows in index order. For large result sets, always check whether an index covers your ORDER BY columns.

Pagination Is Where ORDER BY Gets Honest

The easiest way to feel the tiebreaker problem is to hit it yourself. Write a paginated query, run it twice, and watch what happens when you forget a stable sort key.