go 32 lines · 1 tab

Cursor pagination: opaque tokens with stable ordering

Leah Thompson Jan 2026
1 tab
package paging

import (
  "encoding/base64"
  "encoding/json"
  "time"
)

type Cursor struct {
  CreatedAt time.Time `json:"created_at"`
  ID        string    `json:"id"`
}

func Encode(c Cursor) (string, error) {
  b, err := json.Marshal(c)
  if err != nil {
    return "", err
  }
  return base64.RawURLEncoding.EncodeToString(b), nil
}

func Decode(token string) (Cursor, error) {
  b, err := base64.RawURLEncoding.DecodeString(token)
  if err != nil {
    return Cursor{}, err
  }
  var c Cursor
  if err := json.Unmarshal(b, &c); err != nil {
    return Cursor{}, err
  }
  return c, nil
}
1 file · go Explain with highlit

Offset pagination (LIMIT/OFFSET) is fine until it isn’t: it gets slow on large tables and it produces weird duplicates when rows are inserted between pages. For APIs I prefer cursor pagination with an opaque token. The token encodes the last seen (created_at, id) and the query uses that tuple for stable ordering. The important detail is the “tie breaker” field (id) so you never skip rows when multiple items share the same timestamp. I make the cursor opaque by base64-encoding JSON; you can also sign it if tampering matters. This pattern keeps DB performance predictable and makes frontend infinite scroll stable. It’s also easier to cache because page boundaries don’t shift as data changes.


Related snips

Share this code

Here's the card — post it anywhere.

Cursor pagination: opaque tokens with stable ordering — share card
Link copied