SQLQuokka
Start practicing
QuestionsLearnSandboxFeaturesRoadmapFAQ
SQL Concept

SQL Gaps and Islands: Finding Consecutive Streaks

"Find users who logged in three days in a row" is a classic hard interview question. The rows that form an unbroken run are called an island, and the breaks between them are gaps. One clever trick with ROW_NUMBER turns each island into a group you can count.

Examples run in MySQL 8 and PostgreSQL · Updated

In short

  • Number each user's rows in date order with ROW_NUMBER.
  • Day number minus row number stays constant within a streak.
  • Group by that constant to get one row per streak.
  • Remove duplicate dates first, or the numbering breaks.

The trick

Within a run of consecutive days, the date and the row number both go up by 1 per row, so date − row_number does not change. After a gap the date jumps further than the row number, so the difference changes. That difference is a label for each island.

To keep the examples identical in MySQL and PostgreSQL, the logins all fall in one month and use EXTRACT(DAY FROM login_date) as the day number.

With real dates

Across months, subtract the row number as days instead: DATE_SUB(login_date, INTERVAL rn DAY) in MySQL, or login_date - rn::int in PostgreSQL. The grouping step stays the same.

The example tables

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

logins

user_idlogin_date
12025-03-01
12025-03-02
12025-03-03
12025-03-05
12025-03-06
22025-03-01
22025-03-03
22025-03-04
22025-03-05
22025-03-06
32025-03-10

Examples

Label each island

For user 1, March 1–3 get island 0 and March 5–6 get island 1, because the gap on the 4th shifts the difference.

SELECT user_id, login_date,
       ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn,
       EXTRACT(DAY FROM login_date) - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS island
FROM logins
ORDER BY user_id, login_date;
Result
user_idlogin_daternisland
12025-03-0110
12025-03-0220
12025-03-0330
12025-03-0541
12025-03-0651
22025-03-0110
22025-03-0321
22025-03-0431
22025-03-0541
22025-03-0651
32025-03-1019

One row per streak

Group by user and island to get each streak's first day, last day and length.

WITH numbered AS (
  SELECT user_id, login_date,
         EXTRACT(DAY FROM login_date) - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS island
  FROM logins
)
SELECT user_id, MIN(login_date) AS streak_start, MAX(login_date) AS streak_end, COUNT(*) AS days
FROM numbered
GROUP BY user_id, island
ORDER BY user_id, streak_start;
Result
user_idstreak_startstreak_enddays
12025-03-012025-03-033
12025-03-052025-03-062
22025-03-012025-03-011
22025-03-032025-03-064
32025-03-102025-03-101

Users with a 3-day streak

The longest streak per user, keeping only users with at least three days in a row.

WITH numbered AS (
  SELECT user_id, login_date,
         EXTRACT(DAY FROM login_date) - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS island
  FROM logins
),
streaks AS (
  SELECT user_id, COUNT(*) AS days
  FROM numbered
  GROUP BY user_id, island
)
SELECT user_id, MAX(days) AS longest_streak
FROM streaks
GROUP BY user_id
HAVING MAX(days) >= 3
ORDER BY user_id;
Result
user_idlongest_streak
13
24

Common mistakes

  • Duplicate dates. Two logins on one day get different row numbers but the same date, which splits the streak. Start from SELECT DISTINCT user_id, login_date.
  • Negative numbers in MySQL. ROW_NUMBER() is unsigned in MySQL, so small_number - ROW_NUMBER() can raise an "out of range" error. Subtract in an order that stays positive, or use the date form.
  • Checking only neighbours. Comparing each row with LAG finds where a streak starts but not its length. Combine it with a running count, or use the ROW_NUMBER trick.

Practise Gaps and Islands

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

  1. User Consecutive 3-Day Login Streak

    Detect users who logged into Google services on at least three consecutive calendar days.

    Hard✓ Solved
  2. Longest Login Streaks

    Find users who logged in on at least three consecutive days and report their best streak.

    Hard✓ Solved
  3. Shows at #1 for Three Weeks

    Find TV shows that held the weekly #1 spot for at least three consecutive weeks.

    Hard✓ Solved
  4. Stadium Crowd Streaks

    Find days that belong to a run of three or more consecutive busy days.

    Hard✓ Solved
  5. Job Status Periods

    Collapse daily job results into continuous succeeded and failed periods.

    Hard✓ Solved

Questions people ask

Can I solve it with LAG instead?
Yes. Flag rows where the gap to the previous date is more than one day, take a running SUM of the flags as the streak id, then group by it. Both approaches are common in interviews.
Does this work for numbers, not dates?
Yes. The same trick finds runs of consecutive ids or seat numbers: value minus row number is constant within each run.