Skip to content
← All patterns

social

Game Leaderboard

Who’s at the top?

Per-game high scores, ranked with a secondary index on score.

Scores live under the game partition, one item per player, and a GSI re-keys them by negated score so the top of the leaderboard is a single Query on the index, highest first. A top ten is the same Query with a Limit.

The model

Score

A player's best score in a game.

pk
GAME#<gameId>
sk
PLAYER#<playerId>

Attributes: gameId (S), playerId (S), player (S), score (N), updatedAt (S), country (S), level (N)

Access patterns

  • Query · GSI1Top scores for a game

    List the highest scores in a game, ranked.

  • GetItemGet a player's score

    Fetch one player's score in a game.

  • Query · GSI1Top N players in a game

    The N highest scores in a game: a Query on the index stopped after N items. GSI1SK holds the negated score, so the index's natural ascending order is already highest first.

  • ScanPlayer's rank across games

    Every score one player holds, across games. A Scan reads the whole table to find them, and a filter on the player only trims what comes back, not what is read, so the run here returns every row it read. With a known list of games, a BatchGetItem on each game's key for the player reads only their rows.

Design notes

A GSI to rank, not to look upwhy

The base table answers get-a-player's-score by key. Ranking needs a different sort order, so a GSI re-keys every score under one partition per game, and the whole leaderboard is a single Query on the index.

Negate the score to rank descendinghow

A Query reads a partition in ascending sort-key order unless you pass ScanIndexForward false. Storing the negated score gets the same ranking from the default read, so every caller lands on the leaderboard order without having to remember the flag. The real, positive score stays in its own attribute for display.

Score as the index sort keywhy

DynamoDB returns a Query in sort-key order. With the negated score as GSI1SK, a top N is one Query on the index with a Limit: no scan, and no sorting in the application.

ALL projection, or INCLUDEtrade-off

GSI1 projects every attribute, so a leaderboard row needs no second read. Projecting only what a row shows - player, score, country and level - with INCLUDE would cut the index's storage and write amplification, and a GetItem on the base table, with the key the index already returns, fetches anything else.

The game as the partition keywhy

Each game is its own partition, in the table and in the index, so a top-N read touches one partition. That scales to many games without a hot partition, as long as no single game's writes outgrow what one partition can take.

A larger seed on purposewhy

Applied in full, the record pads Score to 1,000 rows, enough for a game's leaderboard to page through with Limit and LastEvaluatedKey; this page shows the hand-written rows only. A template hint writes each padded row's GSI1SK from that row's own score, so the padded rows rank where their scores put them.

Taught in the course

game-leaderboardDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

List the highest scores in a game, ranked.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
PK(pk)
SK(sk)
GSI1PK
GSI1SK
player
score
country
level
updatedAt
GAME#snake
PLAYER#adaS
GAME#snakeS
-4200N
AdaS
4200N
—
—
—
GAME#snake
PLAYER#bobS
GAME#snakeS
-9100N
BobS
9100N
—
—
—
GAME#snake
PLAYER#cleoS
GAME#snakeS
-6700N
CleoS
6700N
—
—
—
GAME#snake
PLAYER#devS
GAME#snakeS
-8200N
DevS
8200N
—
—
—
GAME#snake
PLAYER#meiS
GAME#snakeS
-5500N
MeiS
5500N
—
—
—
GAME#snake
PLAYER#rajS
GAME#snakeS
-3100N
RajS
3100N
—
—
—
GAME#gm_arcade
PLAYER#usr_aliceS
GAME#gm_arcadeS
-184220N
Alice ChenS
184220N
GBS
42N
2026-10-13T22:42:00ZS
GAME#gm_arcade
PLAYER#usr_bobS
GAME#gm_arcadeS
-172930N
Bob PatelS
172930N
INS
38N
2026-10-13T22:25:00ZS
GAME#gm_arcade
PLAYER#usr_carolS
GAME#gm_arcadeS
-169480N
Carol GarciaS
169480N
MXS
41N
2026-10-13T23:32:00ZS
GAME#gm_arcade
PLAYER#usr_danielS
GAME#gm_arcadeS
-155010N
Daniel SmithS
155010N
USS
36N
2026-10-13T12:41:00ZS
GAME#gm_arcade
PLAYER#usr_elenaS
GAME#gm_arcadeS
-148700N
Elena KimS
148700N
KRS
34N
2026-10-13T21:14:00ZS
GAME#gm_racer
PLAYER#usr_frankS
GAME#gm_racerS
-98240N
Frank JohnsonS
98240N
USS
22N
2026-10-13T19:40:00ZS
GAME#gm_racer
PLAYER#usr_graceS
GAME#gm_racerS
-94812N
Grace AhmedS
94812N
GBS
21N
2026-10-13T11:00:00ZS
GAME#gm_racer
PLAYER#usr_henryS
GAME#gm_racerS
-92155N
Henry LopezS
92155N
MXS
20N
2026-10-13T23:18:00ZS
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.