An endless practice mode for a daily puzzle that never spoils today's answer

An endless practice mode for a daily puzzle that never spoils today's answer

●5 ●37 ●60
calendar_today ago • schedule5 min read

Costcodle is a free daily Costco price guessing game: one item a day for everyone, six guesses, and you win by landing within 5% of the price. A round takes about a minute, and then there is nothing left to play until the next item arrives at 00:00 UTC.

So I built Costcodle Unlimited. It deals random past items with the same rules, as many rounds as you like, with no sign-up and no effect on your daily streak. The catch is that practice rounds come from the same question bank as the daily game, and they must never spoil today's item. This post walks through what a round looks like and how the server keeps that promise.

What an Unlimited round looks like

The page says "Finding a past item..." for a moment, then shows a product photo, its name and an empty receipt headed COSTCODLE UNLIMITED. There is no puzzle number yet. Here is a real round on puzzle #1083 from September 6, 2026, a past puzzle with a published answer:

  1. The item is Kikkoman Untoasted Panko, 25 lb. Read the size before the price: that is a very large bag of breadcrumbs.
  2. Guess $30.00. The receipt says Lower. The guess is 67% off, outside the Close band.
  3. Guess $15.00. The receipt says Close and Higher. Now it is 17% off.
  4. Guess $18.00. Win.

The receipt then reveals the game price of $17.99, the winning range of $17.10 to $18.88, and the original puzzle number, #1083. Every finished puzzle has the same details on Costcodle answers. Prices are snapshots from the original Costcodle game, so they can drift from today's shelf price at your warehouse.

From there, Next item deals another round and Share copies a short result: the words Costcodle Unlimited, your score out of six, one row per guess with a color and an arrow, and a link. It has no puzzle number and no price, so whoever follows the link gets a fresh surprise. If an item does not appeal to you, the Shuffle button on the corner of the photo deals a different one at any time, and a shuffled round is not counted. Games played, wins and average guesses are kept for the visit only.

Stack notes for the engineers in the room

The daily game already kept its answer on the server. Unlimited added a harder question: how do you hand out puzzles from the same bank, at random, without ever handing out the live one?

One definition of "over". A puzzle is in the Unlimited pool when its game date is before today in UTC and the 10 minute grace window for yesterday's puzzle has also passed. So between 00:00 and 00:10 UTC, while someone may still be finishing yesterday's game, yesterday's item is out of the pool too. That rule is a single function, and the server calls it in three places: when it picks candidates, again right before it signs a token (fetching a photo can take a few seconds, and the clock keeps moving), and on every guess when it checks the token. The function ships in the open source rules package, with these lines tested from its README:

import { isUnlimitedEligible } from "costcodle-rules";

isUnlimitedEligible(1121, "2026-10-16T00:05:00.000Z"); // true  (two days ago)
isUnlimitedEligible(1122, "2026-10-16T00:05:00.000Z"); // false (yesterday, still in its grace window)
isUnlimitedEligible(1122, "2026-10-16T00:10:00.000Z"); // true

Random, with few repeats. The browser remembers the last 20 puzzle numbers it was dealt, in memory, and sends them with each start request. The server removes those from the pool and runs a partial Fisher-Yates shuffle to pick 3 candidates, using crypto.getRandomValues with rejection sampling so there is no modulo bias. If the recent list would empty the pool entirely, it deals from the whole pool. A repeat beats no game.

One token, one door. A round in progress is an HMAC signed token with the audience costcodle:unlimited. It is valid for 24 hours after the round starts and does not care about the daily rollover. The daily authenticator refuses Unlimited tokens and the Unlimited authenticator refuses daily ones. The anonymous results endpoint only accepts daily tokens, so practice rounds never show up in the stats other players see for today's item. On every guess the server replays the guesses in the token against the private price, and the price appears in a response only inside an answer field that exists once the round is over.

What I chose not to hide. The token payload is signed and readable, and it contains the puzzle number. I considered encrypting it and decided against it: the product name is on screen during the round, and anyone can search for a name. Encryption would add code and protect almost nothing. The interface simply waits until the round ends to show the number, and the price never goes into the token or the page.

Photos for more than 1,100 past items. The daily game fetches tomorrow's photo at 23:55 UTC, so past puzzles mostly had no stored image. The hourly maintenance job now ends with a backfill step: 60 images per run, two at a time, newest puzzles first, so the archive fills in about 19 hourly runs. Until then, a start can download a missing photo on the spot, with a 15 second limit per image and up to 3 items tried. If none works, the server answers with a retryable 503 and the page shows Try again. The rule that matters: once an image has failed twice, neither the backfill nor an on-the-spot download touches it again. Each image gets 5 attempts in total, so the daily publication of that puzzle always keeps at least 3. Practice traffic can never use up the retries the daily game depends on.

Nothing stored, and a tight budget. Unlimited writes nothing to localStorage or sessionStorage and reports nothing. The site has a 122,880 byte gzip budget for client JavaScript, and when this work started about 6.4 KB of it was left. After moving the Unlimited-only components out of the shared game bundle, the final build came in at 121,981 bytes, with 899 bytes to spare. It runs on the same Cloudflare Worker and the same rate limiter as the daily game, so no new bindings were needed.

Daily game and Unlimited side by side

Topic Daily game Costcodle Unlimited
Items One new item at 00:00 UTC A random past item, as many as you like
Statistics Streaks and win rate saved in your browser Games, wins and average guesses for this visit
Results Reported anonymously unless you turn it off Never reported
Puzzle number Shown from the start Shown after the round ends
Cost Free, no account Free, no account

Costcodle is an independent fan game. It is not affiliated with, endorsed by, or sponsored by Costco Wholesale Corporation.

Your turn

Picking a specific date or puzzle number is not available in this version, and I am still deciding whether it belongs in a practice mode at all. Two questions for the developers here: would you keep the "recently played" list in client memory like this, or track it on the server? And would you let players choose a date, knowing every past answer is already public?

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13

Comparison: Universal Import vs. Plaid/Yodlee

Pocket Portfolio - Mar 12

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

MCP Is the USB-C of AI. So Why Are You Plugging Everything In?

Ken W. Algerverified - Jun 10
chevron_left
5.6k Points • 102 Badges
25Posts
13Comments
4Connections
Make funny things

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!