Skip to content

How long until the next goal? The Exponential distribution

Poisson counts goals. The Exponential distribution times the wait between them, and shows why "we're due a goal" isn't how probability works.

Beginner Part 4 of Statistics Through Football

New to the notation? The symbols explained

Contents

The football question

How long might we wait until the next goal?

I find myself asking this all the time when watching a game, especially Celtic or Spurs.

The concept

The Poisson distribution counts how many goals arrive in 90 minutes. The Exponential distribution looks at the same goals from the other side: the waiting time between them. If goals arrive at a steady average rate, the count is Poisson and the gap is Exponential.

It needs the same single number as Poisson: the rate, λ. A team that averages 2 goals per 90 minutes scores at

$$\lambda = \frac{2}{90} \text{ goals per minute}$$

A football example

What is the chance we're still waiting for a goal after 30 minutes?

$$P(T > t) = e^{-\lambda t}$$

$$P(T > 30) = e^{-\frac{2}{90} \times 30} \approx 0.513$$

Roughly a 51% chance of no goal in the first half hour. A coin toss, for a team that averages two a game.

In plain football

  • \(T\) is how long we wait for the next goal, in minutes.
  • \(t\) is the time we're asking about: 30 minutes here.
  • \(\lambda t\) is how many goals we'd expect in that time: \(\frac{2}{90} \times 30 \approx 0.67\).
  • \(e^{-\lambda t}\) turns that into the chance of none arriving: 0.513.
The wait for a goal, for a team averaging two a game. Short waits are the most common, and the curve falls away steadily. The area under the curve is probability: 49% of it lies before 30 minutes, 51% after.

The average wait isn't the typical wait

The average wait for a goal is \(1 / \lambda = 45\) minutes. But half of all waits are over within 31 minutes. A few very long waits (the 0–0s) drag the average up. So "we usually score within half an hour" and "we average a goal every 45 minutes" are both true of the same team.

The two models agree

The chance of no goal in the full 90 minutes is \(e^{-2} \approx 0.135\): exactly the 13.5% chance of a blank from the Poisson distribution. Count the goals or time the gaps, and you get the same answer.

"We're due a goal"

It's 0–0 after an hour. Surely something's coming?

Under this model, no more than usual. The chance of a goal in the last 30 minutes is 48.7%, exactly the same as in the first 30. The Exponential distribution is memoryless: an hour of nothing doesn't make the next goal any more overdue. The same fallacy catches gamblers who think red is "due" after five blacks.

Show the mathsMemorylessness, the mean and the median. Optional.

If we've already waited s minutes, the chance of waiting a further t is

$$\begin{aligned} &P(T > s + t \mid T > s) \\ &= \frac{e^{-\lambda (s+t)}}{e^{-\lambda s}} \\ &= e^{-\lambda t} = P(T > t) \end{aligned}$$

The past cancels out. That's memorylessness. The same no-memory shows up in how long a manager lasts: a manager in his third season is about as likely to go as one in his first.

The mean wait is \(E[T] = 1/\lambda = 45\) minutes. The median solves \(e^{-\lambda m} = 0.5\):

$$\begin{aligned} m = \frac{\ln 2}{\lambda} &= \frac{0.693}{2/90} \\ &\approx 31.2 \text{ minutes} \end{aligned}$$

Why it matters

Waiting times are everywhere in football:

  • How long until the next goal?
  • How long until the next shot?
  • How long until the next card?
  • How long until the next corner?

The Exponential distribution is the simplest model for all of them, and the starting point for in-play models that update win probabilities minute by minute.

Limitations

  • The rate isn't constant through a match. In the Scottish Premiership and Championship since 2000/01, 55.7% of goals came in the second half and 44.3% in the first. Tiring legs, substitutions and teams chasing the game all raise the rate late on. The real "we're due a goal" feeling isn't entirely wrong; it's just not about being due. Is a goal equally likely in any minute? tests that minute by minute.
  • Game state changes everything. A team that goes a goal up often sits deeper, and the team behind pushes on. One steady λ can't capture that.
  • It's a model for the next goal, not the next great goal. Every goal counts the same, whether it's a tap-in or a 30-yard screamer.

Try it yourself

Next time your team kicks off, note the time of each goal. After a few matches, compare how long you actually waited with the 31-minute median. Then check whether your late goals outnumber your early ones.

Reproduce the analysis

The made-up parts need nothing but Python. The last part, goals by half, reads the results files published by football-data.co.uk: download the Premiership (SC0) and Championship (SC1) files for each season from 2000/01 to 2025/26 with the download script, which names them as the snippet expects; they aren't rehosted on this site. Then:

Show the Python22 lines, ready to copy and run.
# From Football Data Science by Bryan McGuire. Free to use with credit.
# https://www.footballdatascience.co.uk/learn/exponential-distribution
import csv
from math import exp, log

rate = 2 / 90  # goals per minute for a side that averages two a game
print(f"no goal in the first 30 minutes: {exp(-rate * 30):.1%}")
print(f"scored by 30 minutes: {1 - exp(-rate * 30):.0%}")
print(f"average wait {1 / rate:.0f} minutes, median wait {log(2) / rate:.1f}")
print(f"no goal in 90 minutes: {exp(-rate * 90):.1%}")
# memoryless: the chance of a goal in minutes 60-90, given none by 60
print(f"goal in the last 30, given none by 60: {1 - exp(-rate * 90) / exp(-rate * 60):.1%}")

# real goals by half: Premiership (SC0) and Championship (SC1), 2000/01 to 2025/26
first = second = matches = 0
for league in ("SC0", "SC1"):
    for y in range(2000, 2026):
        with open(f"{league}_{y % 100:02d}{(y + 1) % 100:02d}.csv", encoding="latin-1") as f:
            for r in csv.DictReader(f):
                if r.get("HTHG", "").strip() and r.get("FTHG", "").strip():
                    half = int(r["HTHG"]) + int(r["HTAG"])
                    full = int(r["FTHG"]) + int(r["FTAG"])
                    first, second, matches = first + half, second + full - half, matches + 1
print(f"{matches:,} matches: {first / (first + second):.1%} of goals in the first half, {second / (first + second):.1%} in the second")

Further reading

Get the weekly email

One email a week, on Fridays, with everything new, and the occasional note. Unsubscribe in one click. How your email is used.