In an earlier post I used a one-line formula for the value of a casino bonus: winnings minus the expected cost of the wagering. It is fast and it is what most calculators use, ours included. But a single expected value hides the thing players actually experience: most of the time the result is nothing, and once in a while it is a lot.
So I wrote a small Monte Carlo simulation in plain JavaScript and ran 100,000 players through the same offers. The averages barely surprised me. The distributions did.
Note: this is a probability exercise, not advice to gamble. Gambling is 18+, and every simulated slot here has a house edge, exactly like real ones.
A toy slot with a 96% RTP
Real slots do not publish their pay tables, so the model is a simplified slot: a spin pays a multiple of the bet with a fixed probability, and the probabilities are scaled so the return to player is exactly 96%.
const BASE = [[0.5, 0.10], [1, 0.08], [2, 0.05], [5, 0.02], [20, 0.004], [100, 0.0006], [500, 0.00004]]; const raw = BASE.reduce((s, [m, p]) => s + m * p, 0); const PAYS = BASE.map(([m, p]) => [m, p * (0.96 / raw)]); // RTP = 96% function spin(bet) { let r = rand(); for (const [mult, p] of PAYS) { if (r < p) return mult * bet; r -= p; } return 0; } About half of all spins pay something, most wins are small, and a 500x hit comes roughly once in 12,500 spins. rand() is a seeded generator (mulberry32 with seed 42), so every number in this post can be reproduced.
Free spins, then wagering
The offer: 50 free spins at €0.20. The winnings are credited as bonus money, and before you can withdraw them you must bet them a number of times at €0.20 per spin. If the balance drops below one bet, you are out.
function freeSpinsTrial({ spins, bet, wagering, cap }) { let won = 0; for (let i = 0; i < spins; i++) won += spin(bet); if (wagering === 0) return cap ? Math.min(won, cap) : won; let balance = won, wagered = 0; const target = won * wagering; while (wagered < target) { if (balance < bet) return 0; // bust before the wagering is done balance -= bet; wagered += bet; balance += spin(bet); } return cap ? Math.min(balance, cap) : balance; } Run it 100,000 times and look at more than the mean:
| Offer | Mean | Median | Players who get €0 | Top 1% get at least |
|---|---|---|---|---|
| 50 spins, no wagering | €9.53 | €7.60 | 0% | €31.90 |
| 50 spins, no wagering, €20 cap | €8.76 | €7.70 | 0% | €20.00 |
| 50 spins, 35x wagering | €5.58 | €0 | 90.2% | €123.70 |
| 50 spins, 35x wagering, €50 cap | €3.33 | €0 | 90.4% | €50.00 |
Two things stand out.
The no-wagering mean matches the formula. 50 spins x €0.20 x 96% = €9.60, and the simulation lands on €9.53. Good sign that the model behaves.
At 35x the formula and the simulation disagree. The linear estimate says the expected cost of wagering is 35 x winnings x 4%, which is more than the winnings themselves, so the value is zero. The simulation says the average is €5.58. The difference is the bust rule: a player can never lose more than the bonus balance, so the losses are cut off, while the rare lucky run keeps its full upside. The average is pulled up by the top few percent, and the median player walks away with nothing.
That is the honest summary of a 35x free-spin offer: positive on average, nothing for 9 players in 10.
What a cap does to the tail
Look at the capped row. The €50 maximum win barely changes how many players get nothing (90.4%), but it cuts the mean from €5.58 to €3.33. A cap does not hurt the typical player. It removes the lucky outcomes that made the offer worth anything on average. With 35x wagering, the cap is often the most important number in the terms.
Cashback: same money, different shape
The same simulation works for cashback. Take €10 of cashback and change only the wagering attached to it:
| Wagering on €10 cashback | Mean | Median | Players who get €0 |
|---|---|---|---|
| None (paid as cash) | €10.00 | €10.00 | 0% |
| 1x | €9.61 | €7.70 | 0% |
| 10x | €7.60 | €0 | 72.3% |
| 35x | €6.11 | €0 | 89.7% |
Cashback with no wagering is a fixed €10. At 1x it is still close to that and nobody ends with nothing. From 10x on it turns into a lottery ticket: the mean stays respectable, but most players lose the whole amount while trying to clear it.
Takeaways
- Report the distribution, not just the mean. For anything with high wagering, the median and the share of players who get €0 say more than the expected value.
- The linear formula is a floor, not the truth. It ignores that a bust stops your losses, so at high wagering it understates the average and hides how unequal the outcomes are.
- Caps attack the tail. A max-win cap changes little for most players and a lot for the average.
- Low wagering changes the shape. Going from 35x to 1x does not just raise the average, it removes the 90% chance of nothing.
If you want the quick closed-form estimate for a real offer (spins, spin value, wagering, cap and RTP), the free spins value calculator on CasinoKit gives it in one click. Treat it as the starting point, and remember what the single number leaves out.

