SQLQuokka
Start practicing
QuestionsLearnSandboxFeaturesRoadmapFAQ
SQL Concept

SQL LAG and LEAD Functions Explained

LAG looks back at the previous row and LEAD looks ahead to the next, in an order you choose. They replace awkward self joins for questions like "change since the last order", "days between visits" or "month-over-month growth".

Examples run in MySQL 8 and PostgreSQL · Updated

In short

  • LAG(col) returns the value from the previous row; LEAD(col) from the next.
  • PARTITION BY keeps the comparison inside each customer or group.
  • The first row of a partition has no previous row, so LAG is NULL.
  • An optional default replaces that NULL: LAG(col, 1, 0).

Previous and next rows

LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) means: for each order, take the amount of the same customer's previous order by date. The window never crosses into another customer.

A second argument looks further back: LAG(amount, 2) is two orders ago. A third argument sets a default instead of NULL.

Dates between rows

Gaps in days are written differently per database: DATEDIFF(order_date, LAG(order_date) OVER (…)) in MySQL, and plain subtraction order_date - LAG(order_date) OVER (…) in PostgreSQL. The examples below stay with amounts so they run unchanged in both.

The example tables

Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.

orders

order_idcustomer_idorder_dateamount
10112025-01-05120
10212025-01-2080
10322025-01-07200
10412025-02-02150
10532025-02-1090
10622025-02-2560
10732025-03-01130
10812025-03-0340

Examples

Previous order amount

Each customer's first order has no previous order, so prev_amount is NULL.

SELECT customer_id, order_id, amount,
       LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS prev_amount
FROM orders
ORDER BY customer_id, order_date;
Result
customer_idorder_idamountprev_amount
1101120NULL
110280120
110415080
110840150
2103200NULL
210660200
310590NULL
310713090

Change since last order, and the next order

Subtracting LAG gives the change; LEAD shows which order comes next.

SELECT customer_id, order_id, amount,
       amount - LAG(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS amount_change,
       LEAD(order_id) OVER (PARTITION BY customer_id ORDER BY order_date) AS next_order
FROM orders
ORDER BY customer_id, order_date;
Result
customer_idorder_idamountamount_changenext_order
1101120NULL102
110280-40104
110415070108
110840-110NULL
2103200NULL106
210660-140NULL
310590NULL107
310713040NULL

Common mistakes

  • Forgetting PARTITION BY. Without it, the first order of customer 2 is compared with the last order of customer 1.
  • Filtering on LAG in WHERE. Window functions are computed after WHERE. Compute LAG in a CTE, then filter in the outer query.
  • Ignoring the first row. Arithmetic with NULL gives NULL. Decide whether the first row should be dropped, kept as NULL or defaulted with COALESCE.

Practise LAG and LEAD

Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.

  1. Month-over-Month Revenue Growth

    Compute monthly successful payment volume and its percentage change versus the prior month.

    Medium✓ Solved
  2. Current and Previous Subscription Plan

    From a plan-change history, return each user's current plan and the one before it.

    Medium✓ Solved
  3. Ordered Purchase Funnel

    Measure a three-step conversion funnel where the steps must happen in order.

    Medium✓ Solved
  4. Average Days Between Orders

    Measure each repeat customer's average gap between consecutive orders.

    Medium✓ Solved
  5. Advocate Program Long-Call Growth

    Measure month-over-month growth in the number of long support calls.

    Medium✓ Solved
  6. Drivers With Back-to-Back Drops

    Flag drivers whose weekly completed trips fell by 30% or more two weeks in a row.

    Medium✓ Solved

Questions people ask

Are LAG and LEAD available in MySQL?
Yes, since MySQL 8.0, with the same syntax as PostgreSQL.
How do I compare with the same month last year?
Use LAG(value, 12) over months ordered by date, as long as there is exactly one row per month.