datarekha
SQL Medium Asked at MetaAsked at UberAsked at AirbnbAsked at LinkedIn

How do LAG and LEAD work, and how would you use them to compute month-over-month revenue change?

The short answer

LAG accesses a value from a previous row within the partition; LEAD accesses a value from a following row. Both accept an optional offset (default 1) and a default value when the referenced row does not exist. They are the standard tool for period-over-period comparisons without a self-join.

How to think about it

What the interviewer is really asking: “Can you compute period-over-period comparisons without a self-join?” LAG and LEAD are the clean, readable answer. Picture a sliding window that peeks at a neighbouring row — LAG looks back, LEAD looks forward — both scoped by an OVER (PARTITION BY ... ORDER BY ...) clause so the lookback resets per customer, region, or product:

LAG  (column [, offset [, default]]) OVER (PARTITION BY ... ORDER BY ...)
LEAD (column [, offset [, default]]) OVER (PARTITION BY ... ORDER BY ...)
  • offset — how many rows back (LAG) or forward (LEAD); defaults to 1.
  • default — the value returned when the target row doesn’t exist (first/last in the partition); defaults to NULL.

A worked example — month-over-month change

PARTITION BY region keeps each region’s window separate; ORDER BY month defines “previous”; subtracting the lagged revenue gives the change:

SELECT region, month, revenue,
       LAG(revenue) OVER (PARTITION BY region ORDER BY month) AS prev_revenue,
       revenue - LAG(revenue) OVER (PARTITION BY region ORDER BY month) AS mom_change
FROM monthly_revenue
ORDER BY region, month;
regionmonthrevenueprev_revenuemom_change
East2024-013000NULLNULL
East2024-0232003000200
East2024-0338003200600
East2024-0429003800-900
West2024-014000NULLNULL
West2024-0248004000800
West2024-0339004800-900
West2024-04520039001300

Each region’s January row has no predecessor inside its own partition, so prev_revenue is NULL and the arithmetic yields NULL — exactly once per region, not once for the whole table. Everywhere else, revenue − prev_revenue reads off the month-over-month delta: East dips 900 in April, West jumps 1300.

How the pieces generalise

  • The third argument replaces the missing value: LAG(revenue, 1, 0) returns 0 instead of NULL for the first row.
  • LEAD powers “time until next event” — LEAD(session_start) OVER (PARTITION BY user_id ORDER BY session_start) gives each session’s successor.
  • A larger offset reaches further back: LAG(revenue, 12) compares against the same month a year ago.
Learn it properly Window functions

Keep practising

All SQL questions

Explore further

Skip to content