Skip to content

Leaderboard "Active" list shows only 8 unique users instead of 10 — frozen streaks still counted (follow-up to #9984) #10059

Description

@oshliaer

Follow-up to #9984. That issue was closed, but it only patched the UI — the underlying data is still broken.

What's wrong

The Longest Visit Streak → Active list on https://roadmap.sh/leaderboard is meant to show the top 10 users. But two of those 10 rows are duplicates of users who already appear higher up. So the list shows 10 rows but only 8 distinct people.

API: GET https://api.roadmap.sh/v1-list-leaderboard-statsstreaks.active

Why the duplicates happen

Some streaks are frozen: the streak ended, but its row stays in the list with its last value and never changes. The same user also has a second, live row. That duplicate user.id is exactly what #9984 spotted — but #9984 only fixed the React key in the UI. The dead rows are still in the data, so they still take up slots and still push real users out of the top 10.

Proof — 3 snapshots

A live streak grows by +1 every day. A frozen one doesn't move at all. Brofy is the control (single row, no duplicate).

User May 18 Jun 10 Jun 11 Verdict
Brofy (control) 152 175 176 live (+1/day)
Lost (live) 151 175 176 live
IgorLutiy (live) 175 176 live
Lost (ghost) 158 158 158 frozen
IgorLutiy (ghost) 379 379 379 frozen

Both ghost rows haven't moved across 3 snapshots / 24 days, while every live row grew by exactly +1/day. Note: the current #1 in Active is IgorLutiy 379 — a dead streak.

Raw streaks.active — 2026-06-11T09:35:20Z (10 rows, 8 unique users)
  379  IgorLutiy        (6712dee6791f57dd60d309cd)   <- frozen, ghost
  221  Allano Alberto   (6588a30a54b577105138fe61)
  209  Kishore Y        (65026ae83ee87ab4ca51b71e)
  192  Mark Nassef      (692b0cf8bd486ea4602db1d7)
  176  Lost             (679b590f31e842a9fc848267)   <- live
  176  Brofy            (6761b41e8fe51199dac494b4)   <- control
  176  IgorLutiy        (6712dee6791f57dd60d309cd)   <- live (dup of row 1)
  171  Sandesh Hegde    (6583013d5145316d2533c340)
  158  Lost             (679b590f31e842a9fc848267)   <- frozen, ghost (dup of row 5)
  156  Niuempai_oficial (668d9eff8896c6f50b2e1034)
Raw streaks.active — 2026-06-10T17:50:41Z
  379  IgorLutiy        379  (frozen)
  220  Allano Alberto
  208  Kishore Y
  192  Mark Nassef
  175  Lost             (live)
  175  Brofy            (control)
  175  IgorLutiy        (live, dup)
  170  Sandesh Hegde
  158  Lost             (frozen, dup)
  155  Niuempai_oficial

Suggested fix (backend)

When building active, drop any streak whose last visit is older than today (minus a grace day), and pick the current row per user_id — not the one with the max count. That removes the frozen rows and the duplicates in one go, and the list goes back to 10 real users.

A naive dedup by max count would make it worse: it would keep the frozen 158 over the live 176 for Lost, and the dead 379 over the live 176 for IgorLutiy.


UPD 2026-07-07 — the "slot theft" framing is wrong; the real bug is missing dedup by user_id

Fresh streaks.active: 10 rows, 9 unique users. Only one duplicate left — IgorLutiy (379 frozen + 201 live). The Lost 158 ghost is gone.

Why it's gone: it wasn't fixed. Over 26 days live streaks grew ~+1/day, the top-10 cutoff rose to exactly 158 (now Jinx at #10), and Lost's frozen 158 fell below the boundary and dropped out. So frozen rows self-eject once live streaks climb past their value — they don't steal slots forever. Only an anomalously high frozen value persists (IgorLutiy 379 stays #1 regardless).

User Jun 11 Jul 07
IgorLutiy (ghost) 379 379 still #1
IgorLutiy (live) 176 201 +1/day
Lost (live) 176 201 +1/day
Lost (ghost) 158 dropped past cutoff 158

Takeaway: the persistent defect isn't "frozen rows linger" — it's that the top-10 is selected without GROUP BY user_id, so one user takes two slots. 379 provably isn't the current streak (unchanged across 3 snapshots while an active streak must grow +1/day; current is 201). The fix stands — dedup by user_id, keep the current row (not max(count)) — but the lever is the grouping, not a last-visit date filter.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions