When you beat your best score in a browser game, something has to remember it. What that something is — and where it lives — turns out to determine a surprising amount about how a game site can honestly present itself.
This guide covers the actual mechanism, its failure modes, and the consequence that most browser game sites quietly ignore.
The Web Storage API
Browsers expose a simple key-value store called localStorage. It stores strings, it is scoped to the site's origin, it persists across sessions, and — critically — it is never attached to network requests.
That last property is the important one and the most misunderstood. A cookie travels with every request the browser makes to the site, which is what allows a server to recognise a returning visitor. LocalStorage does not travel. It can only be read by JavaScript running on a page from the same origin, and it reaches a server only if a script explicitly sends it.
On this site, no script does. Which means that everything the games remember about you is genuinely, structurally private — not as a policy commitment but as a property of the mechanism.
What gets stored
| Key | Contents |
|---|---|
fa:game:<slug>:bestScore | Your highest score in that game |
fa:game:<slug>:bestFood | Most items collected in one run |
fa:game:<slug>:bestCombo | Longest combo chain |
fa:game:<slug>:bestDistance | Furthest distance travelled |
fa:game:<slug>:runs | How many runs you have played |
fa:favorites | Game slugs you have hearted |
fa:recentlyPlayed | Up to 40 entries of slug, timestamp and play count |
fa:soundPreference | Sound on or off |
fa:reducedMotionPreference | Reduced motion on or off |
The whole record for a game is five small numbers. Thirty games is a few kilobytes — well inside any browser's quota, which is typically several megabytes per origin.
The failure modes nobody plans for
LocalStorage is simple, which encourages developers to treat it as reliable. It is not, and it fails in ways that produce genuinely confusing bugs.
- It can throw on access. Not on write — on access. In some browsers, with site data disabled, merely reading
window.localStorageraises a SecurityError. Code that assumes the property exists crashes before it gets to the try/catch around the write. - Private browsing varies. Some browsers give private windows a working but ephemeral store; others give one with a zero quota that throws on every write.
- Quota can be exceeded. Rare for a few kilobytes of scores, but a shared origin with other content can hit it.
- Stored values can be corrupt. Anything JSON-parsed from storage might have been edited by hand, truncated by a crash, or written by an older version of the code with a different shape.
- Data can vanish between sessions. Browsers may evict storage under pressure, and a user clearing site data removes everything without warning.
The most common bug
A game that reads localStorage.getItem(...) outside a try/catch will throw on load in a browser with site data blocked — and because it happens during initialisation, the whole game fails to start.
The player sees a blank canvas and assumes the game is broken, when in fact everything except the score persistence would have worked perfectly.
A safe storage layer
The fix is to wrap every access, including the initial availability probe, and to degrade to in-memory operation rather than failing.
The structure this site uses is four rules:
- Probe once at startup, inside a try/catch, by writing and removing a test key. Store the result as a boolean.
- Every read and write checks that boolean first and returns a fallback if storage is unavailable.
- Every read is wrapped in try/catch anyway, because a JSON parse can fail even when storage works.
- Every read has a typed fallback. A numeric getter returns a number or the default, never NaN and never a string that will silently corrupt arithmetic later.
The result is that a browser with storage disabled plays every game perfectly. It simply cannot remember anything, which is the correct degradation: the feature that depends on storage is lost, and nothing else is.
Only store improvements
A record store should write only when a record improves. That sounds obvious and is frequently got wrong, usually by storing the most recent run's value rather than the best.
The pattern is a compare-and-set: read the current value, compare, write only if the new value is higher, and return whether it changed. Returning that boolean matters — it is what lets the game-over screen highlight which specific records were beaten, which is a much better reward than a generic congratulation.
One subtlety: the comparison must handle a missing value. A first run has no stored best, so the read has to return zero rather than undefined, or the comparison silently fails and the first record is never written.
Why client-stored scores cannot support a leaderboard
Here is the consequence that most browser game sites avoid discussing.
LocalStorage is under the user's complete control. Anyone can open developer tools and set fa:game:burger-rush:bestScore to any number they like. There is no signature, no validation and no way to distinguish a played score from a typed one.
That means a leaderboard built from client-reported scores is meaningless. It is not weakly secured — it has no security model at all, because the value being reported is authored by the person reporting it.
There are exactly two honest options:
- Run a server that validates play sessions. Genuinely hard. Meaningful validation means replaying inputs server-side or accepting a well-designed heuristic and an ongoing arms race with people who find it interesting to beat.
- Do not publish rankings. Store records locally, label them as local, and say why.
This site takes the second. Every score display here reads "your local best", the popular games page ranks by your own play history rather than an invented global figure, and no page on the site carries an aggregate rating in its structured data — because we have no rating system and inventing one would be a lie told to a search engine.
It is a genuine loss. The social dimension of a shared high-score table was one of the best things about the arcade era. But an invented number is worse than an absent one.
Try it yourself: Endless Food Collector
Six promotion tiers that raise both what food is worth and how much is chasing you.
LocalStorage compared with the alternatives
| Mechanism | Sent with requests? | Capacity | Good for |
|---|---|---|---|
| Cookies | Yes, every request | ~4 KB | Server-side session state |
| LocalStorage | No | Several MB | Client-only preferences and records |
| SessionStorage | No | Several MB | State that should not survive the tab |
| IndexedDB | No | Much larger | Structured data, replays, large assets |
| Cache Storage | No | Large | Offline assets via a service worker |
For a few kilobytes of scores, LocalStorage is the right choice: synchronous, trivially simple and more than large enough. IndexedDB is the correct tool if you ever want to store replays or a large history, but it is asynchronous and considerably more code.
Cookies would be actively wrong here. Sending a score on every request accomplishes nothing except making the request larger and creating a privacy footprint the site does not need.
What a player should know about their own data
- It is on one device, in one browser. Your best score in Chrome on a laptop is invisible to Firefox on the same laptop, and to your phone.
- It can be deleted at any time. Clearing site data removes it. There is no backup, because there is no server-side copy.
- Private windows usually will not keep it. The game plays fine; it just cannot remember.
- Nobody else can see it. Not the site operators, not advertisers, not other visitors.
- You can edit it. Nothing stops you. It is your browser and your data — which is exactly why it cannot support a ranking.
Designing records worth keeping
Since there is no leaderboard, the records have to be motivating on their own terms. Three design decisions help.
- Track several dimensions, not just score. Best score, best food count, best combo, best distance, best time, total runs. A run that scored poorly may still have set a distance record, which keeps progress visible.
- Highlight what improved. The game-over screen marks each beaten record individually rather than showing a generic message.
- Show the records before the run. The ready screen displays your current best, so the target is in mind before you start rather than only after you fail.
Those three together are a reasonable substitute for a leaderboard: the comparison is against yourself, but it is specific, multi-dimensional and always visible.
Key takeaways
- LocalStorage is browser storage scoped to an origin that is never attached to network requests — which makes it structurally private.
- It can throw on access, not just on write. Wrap the availability probe itself, or a browser with site data disabled will crash your game on load.
- Degrade to in-memory operation rather than failing: the game should play perfectly and simply not remember.
- Store only improvements, and return whether a record changed so the game-over screen can highlight specifics.
- Client-stored scores cannot support a leaderboard — the value is authored by the person reporting it, so there is no security model at all.
- The honest options are server-side validation or no rankings. Inventing global numbers is the dishonest third option.
- Without a leaderboard, records need several dimensions, visible highlighting and pre-run display to stay motivating.
References
- MDN Web Docs — Web Storage API — developer.mozilla.org
- MDN Web Docs — Storage quotas and eviction criteria — developer.mozilla.org
Frequently Asked Questions
Where are my high scores stored in a browser game?
In LocalStorage, which is storage belonging to your browser on your device, scoped to the site's address. It is never attached to network requests, so it does not reach a server unless a script explicitly sends it — and on this site none do.
Will my scores follow me to another device?
No. LocalStorage is per-browser and per-device. Your record in Chrome on a laptop is invisible to Firefox on the same machine, and to your phone.
Why do browser games not have global leaderboards?
Because client-stored scores can be edited by anyone with developer tools open. A leaderboard built on them has no security model at all, so the honest options are server-side validation or no rankings.
What happens if I clear my browsing data?
Your scores, favourites and play history are deleted permanently. There is no backup because there is no server-side copy.
Do games work if I block site data?
They should. A well-built game degrades to in-memory operation: everything plays normally and nothing is remembered between sessions. A game that crashes on load with storage blocked has an unguarded storage access.
Is LocalStorage better than cookies for game data?
For this purpose, yes. Cookies are sent with every request, capped around 4 KB, and create a privacy footprint a client-only game does not need. LocalStorage is larger, synchronous and never transmitted.