Keyset Without a Tiebreak — JavaScript Bug Hunt

Inspired by the pagination bug that silently swallows rows on every busy platform: keyset pagination on created_at alone.

  • Language: JavaScript
  • Layer: Database
  • Difficulty: Hard
  • Concepts: Pagination, Ordering
  • Modelled on: Data-export pipelines
  • Visible tests: rows after the cursor timestamp appear; same-timestamp siblings are not skipped; the limit caps the page
  • Reward: 50 XP for a complete fix

Briefing

Inspired by the pagination bug that silently swallows rows on every busy platform: keyset pagination on created_at alone. When several rows share a timestamp (bulk imports do this constantly), the next page skips their siblings.

The cursor carries (created, id) — use both.

Bug report

BUG-KEYSET · Priority: High · Reported by: data eng

nextPage(rows, cursor, limit) — rows pre-sorted by (created, id) ascending:

  • return up to limit rows strictly AFTER the cursor position
  • "after" means created > cursor.created, OR equal created AND id > cursor.id

Observed: three rows imported in the same second; page 2 starts after the whole second — two rows never appear anywhere.

Logs

[export] total rows 9, paginated union 7 (2 lost at ts=500)

The code as shipped

src/query/keyset.js (editable)

// Keyset pagination over rows sorted by (created, id).
exports.nextPage = function (rows, cursor, limit) {
  var out = [];
  for (var i = 0; i < rows.length && out.length < limit; i++) {
    if (rows[i].created > cursor.created) {
      out.push(rows[i]);
    }
  }
  return out;
};

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.