Postmortem: Coin Flip Game

I had been reading about probability and risk management when I came across a coin flip game from Elms. Looks like they demo it at talks and conferences and use it as a lead gen tool. It’s simple, clever, and addictive in the right way. 

But every time I wanted to play again, I had to enter my email address (basically every 60 seconds): 

The frustration that kicked off this project.

That quickly got annoying—I didn’t want to be added to a mailing list just to play. So I decided to build my own version with less friction. The result is a clean, self-contained coin flip simulator that runs locally (and now on GitHub), works instantly, and requires nothing else from the user to play.

Current start page.
Current UI.

Tech stack

I wanted something lightweight that would work in a browser, so I went with plain HTML, CSS, and JavaScript. No React, Vue, or backend. Here’s the folder structure:

coin-flip-game/

  ├── index.html

  ├── css/styles.css

  ├── js/app.js

  ├── js/game.js

  ├── js/results.js

  ├── js/state.js

  ├── js/ui.js

  ├── js/timer.js

  ├── js/export.js

  ├── assets/

app.js handled setup, screen transitions, and event listeners. game.js contained the logic for bets and outcomes. state.js stored global game data like balance and history. results.js generated the summary screen. ui.js took care of visuals and modals. This modular structure made debugging and iteration easier.

The prototype took shape quickly. The player starts with a balance, chooses heads or tails, and places a bet. The coin flips, and the balance adjusts. That was enough to make it playable, but I wanted it to feel more polished. The early versions involved too much clicking, the layout felt too wide, and restarting didn’t clear everything cleanly.

Fixing the flow

The first issue was the layout. The game stretched too far horizontally, so I compressed the padding in CSS and tightened the design to make it more vertical and centered.

Original start page. The layout was too wide.

Then I fixed the restart logic. Pressing Start or Restart now clears all logs, resets the balance, and starts a new session instantly.

Original in-game UI. V1 had the buttons following each other vertically.
I decided to move the buttons to the right to mimic the original on desktop.

Next came keyboard control. Having to click buttons every time slowed the rhythm, so I added keyboard shortcuts. Pressing Enter while typing a bet now submits it. Pressing Space starts or restarts the game from anywhere, unless a modal or input field is active. That made the experience smoother and closer to how you instinctively want to play.

Error handling and edge cases

As the logic matured, small edge cases appeared. Betting your entire remaining balance, for example, would occasionally trigger an invalid bet error because floating-point precision caused a tiny mismatch. That got fixed by allowing a small tolerance so that 0.01 could equal 0.01 within a fraction of a cent.

I was getting this error at first when trying to bet all my chips, which happened to be the lowest amount bettable: a penny.
It threw NaN errors at some point.

Invalid inputs like negative numbers, empty fields, or bets greater than your balance now show gentle modal messages instead of breaking the game. These checks made the system more forgiving without adding unnecessary complexity.

Visual polish

The color palette is mostly white and orange, with orange reserved for main actions like Start or Restart. The buttons for placing bets or finishing the game are secondary. When you reach the end, the results screen lists your balance, number of flips, and outcomes.

Originally, I had included a chart using Chart.js to visualize the running balance. It worked, but it kept breaking — rendering too few points or misreading values. 

Chart concept borrowed from the original game.

After debugging multiple times, I realized the chart added more trouble than value. I scrapped it and replaced it with simple share buttons for Twitter, Facebook, LinkedIn, and WhatsApp. Clean, functional, and less distracting.

Current results page.

The share buttons pull your game data using Javascript (balance, flips, win rate) and let you share it instantly. I was mainly thinking of Wordle’s virality model here—sharing results leads curious readers to play it themselves, which leads to more sharing, etc. 

Publishing it

Once the game worked locally, I wanted it accessible without setup. GitHub Pages made that easy. I initialized a repository, committed everything, and pushed to GitHub under shehuphd/coin-flip-game. Then I enabled GitHub Pages under the repository settings and set the source to the main branch.

A few minutes later, it was live: https://shehuphd.github.io/coin-flip-game/. No servers, databases, or emails needed.

Lessons

In hindsight, this project was more than a coding exercise. It was a case study in friction, simplicity, and user autonomy. The original version from Elms was brilliant in concept, but gated behind a marketing form.

Their decision to require an email address made the experience more transactional than playful. My rebuild stripped away the marketing layer and left only the game, which is what people came for in the first place.

Technically, it reinforced a few lessons:

  1. Small tools are best built small. Plain JavaScript was enough.
  2. Simplicity scales better than cleverness. Every feature added should earn its place.
  3. Edge cases always exist, but the fixes don’t have to be complex. Less is more. 
  4. Shipping has never been easier. 

Build time: ~6 hours

URL: https://shehuphd.github.io/coin-flip-game/

P.S. Elms founders published a book on financial decision-making, which arrived the day I shipped this project. Feel free to check it out!