0) Problem Restatement
Design a platform for a daily puzzle (asked at Uber), like Wordle or a daily crossword. Every day, all users get the same new puzzle. They submit attempts, the server checks them and gives feedback, and a solved puzzle earns a score. Users keep streaks (days in a row solved), see statistics, and compare on leaderboards (global and friends).
Asked at: Uber — 1 candidate report between Apr 2026 and Apr 2026.1) Requirements
- Publish one puzzle per day (scheduled in advance).
- Submit guesses and get feedback (e.g., which letters are right), with limited attempts.
- Scoring (fewer attempts or faster = better), streaks and personal stats.
- Leaderboards: daily global and friends.
- Fair play: answers never leak to the client ahead of time.
1.1 Scale
- 10M daily players, most within a few hours of release. Peak ~50K guesses/sec right after release.
2) APIs
GET /v1/puzzles/today?tz=Asia/Kolkata → { puzzle_id, date, type, board (no answer), max_attempts }
POST /v1/puzzles/{id}/guesses { guess } → { feedback, attempts_left, solved, score? }
GET /v1/me/stats → { streak, max_streak, played, win_rate, distribution }
GET /v1/puzzles/{id}/leaderboard?scope=global|friends&cursor=
3) Architecture
Architecture Diagram
flowchart LR
U["Players"] --> CDN["CDN - puzzle metadata (no answers)"]
U --> API["Game API - stateless"]
API --> PZ[("Puzzle store - answers encrypted")]
API --> AT[("Attempts - by user, puzzle")]
API --> LB[("Redis sorted sets - leaderboards")]
API --> K[("Events")]
K --> ST["Stats + streak updater"]
ST --> SDB[("User stats")]
ADM["Editors"] --> SCH["Puzzle scheduler"]
SCH --> PZ4) Key Design Points
- Server-side checking: the client never receives the answer. Each guess is checked on the server, and feedback is returned. (Wordle originally shipped the answer list in JavaScript, which was easy to cheat.)
- Attempts are stored per
(user_id, puzzle_id)with a count, the guesses and solved status. The server enforcesmax_attempts, and a unique key prevents double-submits from racing (use a conditional update on the attempt count). - Time zones: "today" depends on the user's local date (release at local midnight) or a single global release time. Pick one. Local midnight spreads the load across the day, which is good for spikes.
- Scoring: e.g.,
score = base − attempts_used × penalty − time_bonus. Store it when solved. - Streaks: update on solve. If the last solved date = yesterday (in the user's time zone), streak + 1, otherwise reset to 1. The streak "expires" if a day is missed, which is checked lazily when the user plays or reads stats.
- Leaderboards: a Redis sorted set per puzzle
lb:{puzzle_id}(score → user). Friends leaderboard = fetch friends' scores for that puzzle (a small set) and sort. Persist final results to the DB.
5) Handling the Spike
- Puzzle metadata (without the answer) is cached at the CDN, since it's identical for everyone.
- The game API is stateless and pre-scaled before release. Answers are cached in memory on API servers (loaded securely at release time).
- Attempts are written to a partitioned store (by user), which spreads writes evenly.
- Stats updates are asynchronous via events, so the guess path stays fast.
6) Wrap-Up
Schedule puzzles in advance and serve today's puzzle (without the answer) from a CDN, check every guess on the server with enforced attempt limits stored per user and puzzle, and compute scores on solve. Update streaks and stats asynchronously with time-zone-aware day logic, keep leaderboards in Redis sorted sets (with friends' boards computed from small friend lists), and spread or pre-scale for release spikes.