Is the lottery worth playing?

Date: 2022-10-14 | finance | reflect | lottery |

Every now and then I get the urge to play the lottery - like when Megamillions hits big milestones of $0.5B or $1B and everyone and their robot is posting affiliate links to buy them.

But when is a good time to actually play?

The math behind the lottery

My gut instinct is telling me the answer is never but the answer is likely more nuanced than that.

Let's try to answer this by:

  • Finding the expected value of the lottery
  • Compare this with other means of value acquisition
  • Take into account other effects

The expected value of a lottery ticket

I think we'll use expected value to try and determine the value of the lottery ticket we buy. When we compare the expected value of the ticket to the cost of buying the ticket we should end up with a good understanding of whether it's worth it or not.

The definition of expected value (via Investopedia) is:

"Expected value (EV) describes the long-term average level of a random variable based on its probability distribution."

In simple terms we're basically saying:

  • What are all the outcome costs / benefits?
  • What are the chances of each outcome happening?
  • Thus what is the expected value of doing this thing?

We can calculate this by:

  • expectedValue = sum(allOutcomeExpectedValues)
    • outcomeExpectedValue = probabilityOfOutcome * valueOfOutcome

So bringing this back to determining if lottery tickets are worth it, we can set up our formula like this:

  • Scenario: I'm going to buy a lottery ticket for $ticketPrice
    • Outcome A: I win and get $lotteryWinnings
      • Probability: oddsOfWinning
      • Value: lotteryWinnings - ticketPrice
    • Outcome B: I lose and get $0
      • Probability: oddsOfLosing = 1 - oddsOfWinning
      • Value: 0 - ticketPrice

We can formalize this in fsharp code like this:

type OutcomeExpectedValue = {
    Probability: float
    Value: float }

let calculateExpectedValue (allOutcomeExpectedValues : OutcomeExpectedValue list) : float =
    allOutcomeExpectedValues
    |> List.map (fun outcome -> outcome.Probability * outcome.Value)
    |> List.sum

let calculateExpectedValueOfLotteryTicket (ticketPriceUsd : float) (oddsOfWinning : float) (winPayoutUsd : float) : float =
    let oddsOfLosing = (float 1) - oddsOfWinning 

    let winOutcome = {
        Probability = oddsOfWinning
        Value = winPayoutUsd - ticketPriceUsd
    }

    let loseOutcome = {
        Probability = oddsOfLosing
        Value = (float 0) - ticketPriceUsd
    }

    [ winOutcome ; loseOutcome ]
    |> calculateExpectedValue

// Megamillions
// * Odds: 1 / 303M - https://www.nj.com/lottery/2022/07/mega-millions-odds-how-to-play-from-the-massive-128b-jackpot-to-8-smaller-prizes.html#:~:text=The%20dream%20for%20a%20lottery,a%20preposterous%20302%2C575%2C350%20to%201. 
// * Payout: $494M (2022.10.14)
let megaMillionsExpectedValue = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) (float 500000000)
printfn "MegaMillionsEV: %A" megaMillionsExpectedValue

Running this on 2022.10.14's Megamillions numbers nets us with -0.349 or a loss of $0.35 per ticket.

Now the interesting thing here is that this is actually the best case scenario - in reality we'll never see the full amount due to federal, state, city (if in places like NYC), and misc expenses (like you prob want a lawyer / attorney if you win).

Further reading: What you should do if you win the lottery via Reddit.

Okay so how do we account for all these other expenses? Probably with a lot of work, but I think we can get close enough by just cutting winnings by 50% - this leaves us a bit on the lowside but better that than the opposite.

So our updated calculations for Megamillions will look something like this:

// We take 50% off winnings amount because #taxes and #miscexpenses
let megaMillionsWOMiscExpensesEV = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "megaMillionsWOMiscExpensesEV: %A" megaMillionsWOMiscExpensesEV

This makes things look quite a lot worse with an expected value of -$1.17! Another way to think about this is we're buying something worth about $0.83 but paying $2.00 for it - not as bad a deal as a cup of coffee, but still not a good deal for us.

When does a lottery ticket make sense?

So assuming we don't want to lose money when we play the lottery (on average) then we need to find values at which the lottery ticket presents us with a positive expected value.

We could probably do some fancy algebra to do this but tbh I forgot how to do that so instead I'm just going to brute force find this. It's not the most efficient big O wise, but this is a small dataset and the most expensive thing here is me so that's worth optimizing for.

We can model this in fsharp by creating a new function that iterates over expected winning values until it finds a value that leads to a non-negative expected value:

let calculateMinimumWinningsForLotteryTicketBreakEvent (winningsToOutcomeFn : float -> float) (initialWinnings : float) (stepSize : float) : float =
    let rec getBreakEvenLoop (count : int) =
        let currentWinningsAmount = (initialWinnings + (initialWinnings * stepSize * (float count)))
        let currentEV = winningsToOutcomeFn currentWinningsAmount
        
        match currentEV with 
        | ev when ev > (float 0) -> currentWinningsAmount
        | _ -> getBreakEvenLoop (count + 1)

    getBreakEvenLoop 0

We are getting a little fancy here with our #functionalprogramming but the two probably confusing parts are:

  • (winningsToOutcomeFn : float -> float) - This tells fsharp that I'm going to give it a function that takes in a float and returns a float. This is a great example of how functional paradigms lead to easier to compose (often via delegation) code. We'll see this in action in a sec.
  • let rec getBreakEvenLoop (count : int) = - this is just a normal inline function but I've passed a rec keyword so it can call itself. More in Recursive Functions: The rec Keyword

Now that we have this function, we can call it with:

let megaMillionsWOMiscExpensesWinningsToOutcomeFn = (calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000))
let megaMillionsWOMiscExpensesBreakEvenWinnings = calculateMinimumWinningsForLotteryTicketBreakEvent megaMillionsWOMiscExpensesWinningsToOutcomeFn (((float 500000000) * 0.5)) (float 0.1)
printfn "MegaMillions break even winnings: %A" megaMillionsWOMiscExpensesBreakEvenWinnings
printfn "Megamillions prize pool to break even: %A" (megaMillionsWOMiscExpensesBreakEvenWinnings * (float 2))

This prints out:

MegaMillions break even winnings: 625000000.0
Megamillions prize pool to break even: 1250000000.0

This is telling us that the amount we need to win from MegaMillions to make it worthwhile is $625M. But remember that we said that actual winnings are cut in half from the winnings they say you'll get, so in reality we need twice that number or an advertised prize pool of about $1250M ($1.25B) to break even!

If we look at the Top 10 Mega Millions Jackpots, we see that there have only been 2 jackpots greater than our break even price of $1250M in the history of Megamillions. That's a lot of lost expected value!

What should you do with that money instead?

Okay so we know the lottery is a bad value. What should we do with it instead?

The nice thing is that the lottery seems like such a bad value that pretty much anything you spend your money on that you think is "worth" the money you put in is a better way to spend money =)

My top 3 would probably be:

Okay but I still want to buy a lottery ticket

TBH even with these great reasons not to buy a lottery ticket I still want to buy a lottery ticket. Are there reasons I should do it anyway?

Sure we can fool ourselves into making this a decent decision:

  • We don't want FOMO (aka regrets)
  • We want the #thrill of government-endorsed gambling
  • Lottery money is typically spent on public good projects - so on one hand it's kinda like charity without the tax breaks, on the other hand it's kinda more tax (but for good, right?)
  • You can get free lottery tickets (and support me and my writing)!
    • Kinda - if you use my Jackpocket referral link, you get free tickets and I get free tickets when you play!
    • Free tickets mean worst case is a $0.00 expected value and actual expected value is $0.83 - what a steal!

Thanks for reading and please use your money responsibly so you can buy yourself the Financial Independence and freedom you deserve.

Full Source Code

Here's the full notebook I created in fsharp:

type OutcomeExpectedValue = {
    Probability: float
    Value: float }

let calculateExpectedValue (allOutcomeExpectedValues : OutcomeExpectedValue list) : float =
    allOutcomeExpectedValues
    |> List.map (fun outcome -> outcome.Probability * outcome.Value)
    |> List.sum

let calculateExpectedValueOfLotteryTicket (ticketPriceUsd : float) (oddsOfWinning : float) (winPayoutUsd : float) : float =
    let oddsOfLosing = (float 1) - oddsOfWinning 

    let winOutcome = {
        Probability = oddsOfWinning
        Value = winPayoutUsd - ticketPriceUsd
    }

    let loseOutcome = {
        Probability = oddsOfLosing
        Value = (float 0) - ticketPriceUsd
    }

    [ winOutcome ; loseOutcome ]
    |> calculateExpectedValue

// Megamillions
// * Odds: 1 / 303M - https://www.nj.com/lottery/2022/07/mega-millions-odds-how-to-play-from-the-massive-128b-jackpot-to-8-smaller-prizes.html#:~:text=The%20dream%20for%20a%20lottery,a%20preposterous%20302%2C575%2C350%20to%201. 
// * Payout: $494M (2022.10.14)
let megaMillionsExpectedValue = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) (float 500000000)
printfn "MegaMillionsEV: %A" megaMillionsExpectedValue

// We take 50% off winnings amount because #taxes and #miscexpenses
let megaMillionsWOMiscExpensesEV = calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "megaMillionsWOMiscExpensesEV: %A" megaMillionsWOMiscExpensesEV

let calculateMinimumWinningsForLotteryTicketBreakEvent (winningsToOutcomeFn : float -> float) (initialWinnings : float) (stepSize : float) : float =
    let rec getBreakEvenLoop (count : int) =
        let currentWinningsAmount = (initialWinnings + (initialWinnings * stepSize * (float count)))
        let currentEV = winningsToOutcomeFn currentWinningsAmount
        
        match currentEV with 
        | ev when ev > (float 0) -> currentWinningsAmount
        | _ -> getBreakEvenLoop (count + 1)

    getBreakEvenLoop 0

let megaMillionsWOMiscExpensesWinningsToOutcomeFn = (calculateExpectedValueOfLotteryTicket (float 2) (float 1 / float 303000000))
let megaMillionsWOMiscExpensesBreakEvenWinnings = calculateMinimumWinningsForLotteryTicketBreakEvent megaMillionsWOMiscExpensesWinningsToOutcomeFn (((float 500000000) * 0.5)) (float 0.1)
printfn "MegaMillions break even winnings: %A" megaMillionsWOMiscExpensesBreakEvenWinnings
printfn "Megamillions prize pool to break even: %A" (megaMillionsWOMiscExpensesBreakEvenWinnings * (float 2))

let megaMillionsWOMiscWoTicketExpensesEV = calculateExpectedValueOfLotteryTicket (float 0) (float 1 / float 303000000) ((float 500000000) * 0.5)
printfn "EV without ticket price: %A" megaMillionsWOMiscWoTicketExpensesEV

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.