Interview Prep Window Functions

SQL Window Functions Interview Questions (2026): 6 Real Questions With Runnable Answers

SQL window functions interview questions with answers — ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD, running totals, percent of total. Covers the patterns asked at FAANG and high-bar data roles in 2026.

Apr 29, 2026 10 min read

Overview

Window functions are the single highest-signal SQL topic in interviews — analysts and data engineers who can use them fluently get hired faster. The questions below cover the six window-function patterns interviewers test most: top-N per group, ranking ties, running totals, lag/lead comparisons, percent of total, and gap detection. Each answer is runnable on PostgreSQL; MySQL 8+ and BigQuery support the same syntax with minor changes.

Questions & Answers

Q1. Top 3 products per category by revenue Common · Medium

Prompt: Given orders(product_id, total) and products(id, name, category), return the top 3 products in each category by total revenue.

SELECT category, name, total_revenue
FROM (
  SELECT
    p.category,
    p.name,
    SUM(o.total) AS total_revenue,
    ROW_NUMBER() OVER (
      PARTITION BY p.category
      ORDER BY SUM(o.total) DESC
    ) AS rn
  FROM orders o
  JOIN products p ON p.id = o.product_id
  GROUP BY p.category, p.name
) ranked
WHERE rn <= 3
ORDER BY category, total_revenue DESC;

Why this matters: PARTITION BY divides the result into per-category windows. ROW_NUMBER assigns 1..N within each window. The outer WHERE rn <= 3 keeps only the top three. This is the canonical "top-N per group" pattern — practice it until you can write it from muscle memory.

Q2. RANK vs DENSE_RANK vs ROW_NUMBER Common · Medium

Prompt: For employees(id, salary), explain the difference between ROW_NUMBER, RANK, and DENSE_RANK by ranking salaries. What happens with ties?

SELECT
  id,
  salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
  RANK()       OVER (ORDER BY salary DESC) AS rank_,
  DENSE_RANK() OVER (ORDER BY salary DESC) AS dense
FROM employees;

Why this matters: When two employees tie on salary: ROW_NUMBER assigns 1, 2 (arbitrary tiebreak); RANK assigns 1, 1, 3 (skips 2); DENSE_RANK assigns 1, 1, 2 (no skip). Pick DENSE_RANK for "second-highest distinct value" questions, ROW_NUMBER for "exactly N rows" questions.

Q3. Running total of revenue by day Common · Medium

Prompt: Given orders(order_date, total), compute the cumulative revenue total at each day.

SELECT
  order_date,
  daily_revenue,
  SUM(daily_revenue) OVER (
    ORDER BY order_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM (
  SELECT order_date, SUM(total) AS daily_revenue
  FROM orders
  GROUP BY order_date
) daily
ORDER BY order_date;

Why this matters: The frame clause `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` is what makes it a running total instead of a grand total. Default frames vary by dialect, so always specify the frame explicitly in interviews.

Q4. Month-over-month revenue change Common · Medium

Prompt: Compute month-over-month revenue change as both absolute delta and percent.

WITH monthly AS (
  SELECT
    DATE_TRUNC('month', order_date) AS month,
    SUM(total) AS revenue
  FROM orders
  GROUP BY 1
)
SELECT
  month,
  revenue,
  LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue,
  revenue - LAG(revenue) OVER (ORDER BY month) AS mom_delta,
  ROUND(
    100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
      / NULLIF(LAG(revenue) OVER (ORDER BY month), 0),
    2
  ) AS mom_pct_change
FROM monthly
ORDER BY month;

Why this matters: LAG returns the previous row's value. NULLIF(..., 0) prevents division-by-zero when the prior month had zero revenue. The first row will have NULL for prev_month_revenue, which is correct.

Q5. Percent of total per category Common · Medium

Prompt: For each product, show its revenue and what percent it contributes to its category total.

SELECT
  category,
  product_id,
  product_revenue,
  ROUND(
    100.0 * product_revenue
      / SUM(product_revenue) OVER (PARTITION BY category),
    2
  ) AS pct_of_category
FROM (
  SELECT
    p.category,
    p.id AS product_id,
    SUM(o.total) AS product_revenue
  FROM orders o
  JOIN products p ON p.id = o.product_id
  GROUP BY p.category, p.id
) per_product
ORDER BY category, pct_of_category DESC;

Why this matters: SUM(...) OVER (PARTITION BY category) gives the category total on every row, so each row can be divided by it without a self-join. This pattern appears in dashboards constantly.

Q6. Detect gaps in a sequence Common · Hard

Prompt: Given event_log(event_date), find dates where there is a gap of 2+ days from the previous event.

SELECT
  event_date AS gap_end,
  prev_event AS gap_start,
  event_date - prev_event AS gap_days
FROM (
  SELECT
    event_date,
    LAG(event_date) OVER (ORDER BY event_date) AS prev_event
  FROM (SELECT DISTINCT event_date FROM event_log) e
) gaps
WHERE event_date - prev_event >= 2
ORDER BY event_date;

Why this matters: LAG combined with date arithmetic lets you compare each row to its predecessor without a self-join. Always DISTINCT first when the underlying table has duplicates per date — otherwise LAG compares within duplicates and the "gap" calculation is off.

Common Pitfalls

  • Forgetting the frame clause — default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW for ordered windows but UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for unordered ones. Always specify.
  • Using GROUP BY when you want a window function — GROUP BY collapses rows; window functions preserve them. If you need both row-level detail and an aggregate side by side, you want a window function.
  • Filtering on a window function in WHERE — WHERE runs before window functions. Wrap in a subquery or CTE and filter outside.
  • Tied results with ROW_NUMBER — ROW_NUMBER picks an arbitrary winner among ties. Use DENSE_RANK for stable distinct-value ranking.
  • PARTITION BY on a column that allows NULL — NULL becomes its own partition. Often unintended; coalesce or filter first.

Practice With AI2SQL

The fastest way to lock in these patterns is to write them by hand, then check against AI2SQL output. Generate variations of every question above with different schemas, rewrite them from scratch, and you will have these patterns memorized within a week.

Try AI2SQL Free

No credit card required

Frequently Asked Questions

Are window functions standard SQL?

Yes — window functions are part of the SQL:2003 standard. PostgreSQL, SQL Server, Oracle, BigQuery, Snowflake, and MySQL 8+ all support the core functions: ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM/AVG/COUNT OVER, FIRST_VALUE, LAST_VALUE, NTH_VALUE, NTILE.

When should I use a window function vs a GROUP BY?

Use a window function when you need both the original row detail and an aggregate side by side (e.g., "show each order with its customer total"). Use GROUP BY when you only need the aggregate and the per-row detail is redundant.

Are window functions slow?

They can be slower than GROUP BY for huge tables because they require sorting per partition. Indexes on PARTITION BY and ORDER BY columns help. Most modern engines handle window functions well up to hundreds of millions of rows.

How can I practice window functions interactively?

AI2SQL generates window-function queries from plain English so you can study the patterns, then rewrite them by hand. The compare-then-rewrite loop builds fluency faster than passive reading.

Practice SQL Interview Questions Instantly

Generate variations of every question above against your own schema. AI2SQL writes the answer; you rewrite it by hand to lock the pattern in.

Try AI2SQL Free

No credit card required