Growth and Doubling

How Epidemics Spread

Chapter 2 · Growth and Doubling

How Epidemics Spread 1 ended on the boarding-school curve: 3 boys in bed on the first day, then 8, 26, 76 and 225. The day-to-day ratios are 2.67, 3.25, 2.92 and 2.96, so the number in bed roughly tripled each day. That is exponential growth: the amount added each day is proportional to the amount already there. This chapter turns that observation into three tools. The first is the doubling time, a number you can feel. The second is the reproduction number R0, the number that says whether an outbreak grows at all. The third is a short Python simulation that shows why growth cannot go on for ever. There is no calculus here: only multiplication, powers, and the logarithm button on a calculator (or Python’s math.log).

What was run, and what to trust
Every Python snippet in this chapter and in the three exercise solutions was executed, and the output in the comments is what Python printed. The definition of R0, the quoted R0 ranges for measles and seasonal influenza, and the doubling-time formula and rule of 70 come from the Wikipedia articles on the basic reproduction number and on doubling time, fetched with a tool that often returns truncated pages or model-written summaries; the R0 page was longer than the part the tool read, so treat the wording as a paraphrase and the ranges as one source’s figures, not settled facts. That article does not give a formula linking R0 to the growth rate, so the link used below (a fixed generation time) is a modelling assumption of this course, not a quotation. Growth rates, R0 values, populations and generation times in the code are assumed or illustrative: they show a method and are not measurements of any real disease.

Linear Versus Exponential

Suppose a town adds 10 cases every day. After 30 days there are 300: the line climbs steadily. Now suppose instead that the number of cases grows by 25% a day, starting from 10. The code below shows that after 31 days there are more than 10,000. Linear growth adds the same amount each step; exponential growth multiplies by the same factor. Disease is exponential at the start because each case infects others, and those infect others in turn: the more cases there are, the more new ones appear.

Linear growthExponential growth
Ruleadd the same amount each stepmultiply by the same factor each step
Example (illustrative)10, 20, 30, 40, 5010, 20, 40, 80, 160
Time to doublekeeps getting longeralways the same
On a log chartcurves flatter and flattera straight line
Disease picturea steady source, such as a polluted wellperson-to-person spread with no limit yet

Doubling Time

The doubling time is how long it takes for the quantity to double. For a growth of r% per period, the Wikipedia article gives the doubling time as t × ln(2) / ln(1 + r/100), where ln is the natural logarithm (just a button on the calculator, or math.log in Python). It also gives the rule of 70: divide 70 by the percentage growth per period for a quick estimate. At 2% a day, 70 / 2 = 35 days. The article says the rule is accurate to within 10% for growth rates below 25%. The table printed below checks that claim and shows where it fails.

# Doubling time from a constant daily growth rate, and the rule of 70. # The growth rates are illustrative, not measurements. import math print("daily growth exact doubling rule of 70 error") for pct in [1, 2, 5, 10, 25, 40]: exact = math.log(2) / math.log(1 + pct / 100) rule = 70 / pct print(f"{pct:9d}% {exact:11.2f} d {rule:9.2f} d {abs(rule - exact) / exact:6.1%}") cases = 10 days = 0 while cases < 10_000: cases *= 1.25 days += 1 print(f"10 cases growing 25% a day pass 10,000 after {days} days") # daily growth exact doubling rule of 70 error # 1% 69.66 d 70.00 d 0.5% # 2% 35.00 d 35.00 d 0.0% # 5% 14.21 d 14.00 d 1.5% # 10% 7.27 d 7.00 d 3.7% # 25% 3.11 d 2.80 d 9.9% # 40% 2.06 d 1.75 d 15.1% # 10 cases growing 25% a day pass 10,000 after 31 days

The rule of 70 is off by only 0.5% at 1% a day, and by 3.7% at 10% a day. At 25% it reaches 9.9%, just inside the article’s limit, and at 40% a day the error is 15.1%, so the rule stops being a good shortcut. Early epidemics can grow faster than 25% a day, which is why the exact formula is worth keeping. The practical lesson of the last line: even at 25% a day, 10 cases become 10,000 in a month. A doubling time of 3 days means a thousandfold increase in about 30 days, because ten doublings multiply a number by 1,024.

The Reproduction Number R0

The basic reproduction number, R0 (“R naught”), is, in the article’s words, the expected number of cases directly generated by one case in a population where everyone is susceptible. It has no units: it is a count of people, not a rate. The key threshold comes from the same source: in common infection models, R0 above 1 means the infection can spread and the outbreak will expand, while R0 below 1 means it dies out. The article adds a caution that some models, such as those for diseases carried by insects, can sustain an outbreak even with R0 below 1. It also stresses that R0 by itself does not tell you how fast an infection spreads, because speed depends on how long each generation of infection takes.

R0What it meansOutcome in the simple model
Below 1each case infects fewer than one other, on averagethe outbreak shrinks and dies out
Exactly 1each case replaces itselfa steady trickle, neither growing nor shrinking
Above 1each case infects more than one otherthe outbreak grows, and the larger R0 is, the faster
Measles (one source)quoted range 12 to 18among the highest figures for any common infection (check)
Seasonal influenza (one source)quoted as 1.3, range 1.2 to 1.4growth per generation is much slower

A caution about the last two rows: R0 is not a fixed property of a germ. It depends on how people mix, how crowded they are, and what they do, so different studies report different values for the same disease, and an R0 measured in one country need not hold in another. Once some people are immune or have changed their behaviour, the average number of new infections per case is called the effective reproduction number, often written R or Rt (check the naming; later chapters use it). R0 is the special case where everyone can still be infected.

Generations

The simplest way to see what R0 does is to count by generations. The first case is generation 0. Its R0 contacts are generation 1, their contacts are generation 2, and so on. If every case infects exactly R0 others, generation g has R0g new cases. The code below prints eight generations for five invented values of R0. It ignores the fact that people get used up, so it is only valid early on.

# Cases by generation when every case infects R0 others (no one is used up yet). # The R0 values are illustrative; the generation count is a model idea, not a clock. for r0 in [0.8, 1.0, 1.5, 2.5, 6.0]: total = 1.0 new = 1.0 row = [] for generation in range(1, 9): new *= r0 total += new row.append(round(new, 1)) print(f"R0 {r0:3.1f}: new cases per generation {row} total after 8: {round(total)}") # R0 0.8: new cases per generation [0.8, 0.6, 0.5, 0.4, 0.3, 0.3, 0.2, 0.2] total after 8: 4 # R0 1.0: new cases per generation [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] total after 8: 9 # R0 1.5: new cases per generation [1.5, 2.2, 3.4, 5.1, 7.6, 11.4, 17.1, 25.6] total after 8: 75 # R0 2.5: new cases per generation [2.5, 6.2, 15.6, 39.1, 97.7, 244.1, 610.4, 1525.9] total after 8: 2542 # R0 6.0: new cases per generation [6.0, 36.0, 216.0, 1296.0, 7776.0, 46656.0, 279936.0, 1679616.0] total after 8: 2015539

Compare the rows. With R0 of 0.8 the outbreak withers: 0.8 new cases in the first generation and 0.2 by the eighth. At exactly 1.0 it ticks along at one case per generation. At 1.5 it reaches about 26 new cases in the eighth generation, at 2.5 about 1,526, and at an R0 of 6.0 it reaches about 1.7 million. Small differences in R0 become huge differences in size within a few generations, which is why a measured R0 of 1.3 and one of 1.5 are not interchangeable. Note that a generation is a model unit and not a day: how long it takes is the next question.

From R0 to a Growth Rate

To turn generations into days you need the generation time: the time from one person being infected to the people they infect. Suppose, as a simplification, that it is the same fixed number of days T for everyone (assumed: real generation times vary from person to person, and later chapters will deal with that, including the serial interval). Then the number of cases is multiplied by R0 every T days, so the growth per day is R0 raised to the power 1/T, and the doubling time is T × ln(2) / ln(R0). The code uses a generation time of 4 days, an assumed value, to show how doubling time falls as R0 rises.

# From R0 and the generation time to a growth rate and doubling time. # Assumed: every case infects people exactly T days after being infected. # Then cases grow by a factor R0 every T days, so r = ln(R0) / T per day. import math T = 4 # assumed generation time in days print(" R0 daily growth doubling time") for r0 in [1.2, 1.5, 2.0, 3.0, 5.0]: r = math.log(r0) / T doubling = math.log(2) / r print(f"{r0:5.1f} {math.exp(r) - 1:9.1%} {doubling:9.1f} d") # Check the formula by stepping day by day at the daily growth rate for R0 = 2. growth = 2.0 ** (1 / T) - 1 # the daily rate that gives a factor of 2.0 per T days cases = 1.0 for day in range(1, 13): cases *= 1 + growth if day % T == 0: print(f"day {day:2d}: {cases:.2f} new cases a day (a factor of 2 every {T} days)") # R0 daily growth doubling time # 1.2 4.7% 15.2 d # 1.5 10.7% 6.8 d # 2.0 18.9% 4.0 d # 3.0 31.6% 2.5 d # 5.0 49.5% 1.7 d # day 4: 2.00 new cases a day (a factor of 2 every 4 days) # day 8: 4.00 new cases a day (a factor of 2 every 4 days) # day 12: 8.00 new cases a day (a factor of 2 every 4 days)

With an R0 of 2 and a 4-day generation, the number of new cases doubles every 4 days, as it must, because R0 is the factor per generation. With an R0 of 1.2 the same disease takes about 15 days to double; with an R0 of 5 it doubles in about 1.7 days, growing nearly 50% a day. The same R0 with a different generation time would give a different clock: halving T halves every doubling time in the table. That is why two diseases with the same R0 can feel very different, and why a quick-passing infection is harder to catch up with than a slow one.

Why Growth Cannot Go On

The mathematics so far says that outbreaks grow for ever, and that is plainly false. The final code block adds the missing piece: as people become infected they cannot be infected again, so the supply of susceptible people shrinks. Each case now infects only R0 × (the fraction still susceptible) others. The population is a closed community of 10,000, R0 is 2.5, the generation is 4 days, and there is one starting case, all assumed. The model is crude: a person who has been infected simply drops out of the susceptible pool. How Epidemics Spread 3 replaces it with a proper compartment model.

# Growth meets a limit: a closed population of 10,000 (assumed), R0 = 2.5 (assumed), # generation time 4 days (assumed). New cases = R0 x susceptible fraction x # cases one generation earlier. Run it and watch the doubling time stretch out. import math population = 10_000 r0 = 2.5 T = 4 susceptible = population - 1.0 new = [1.0] + [0.0] * (T - 1) for day in range(T, 80): fraction = susceptible / population cases = min(susceptible, r0 * fraction * new[day - T]) susceptible -= cases new.append(cases) for day in range(T, 80, 4): if day >= 8 and new[day - 4] > 0.5: ratio = new[day] / new[day - 4] label = "growing" if ratio > 1 else "shrinking" print(f"day {day:2d} new {new[day]:8.1f} ratio to 4 days ago {ratio:5.2f} {label}") peak = max(range(len(new)), key=lambda d: new[d]) print("peak day:", peak, "| peak new cases:", round(new[peak])) print("ever infected:", round(population - susceptible), "of", population) print(f"naive 100-day prediction, no limit: {r0 ** (100 / T):.2e} cases") # day 8 new 6.2 ratio to 4 days ago 2.50 growing # day 12 new 15.6 ratio to 4 days ago 2.50 growing # day 16 new 38.9 ratio to 4 days ago 2.49 growing # day 20 new 96.6 ratio to 4 days ago 2.48 growing # day 24 new 237.7 ratio to 4 days ago 2.46 growing # day 28 new 570.6 ratio to 4 days ago 2.40 growing # day 32 new 1288.3 ratio to 4 days ago 2.26 growing # day 36 new 2493.6 ratio to 4 days ago 1.94 growing # day 40 new 3272.2 ratio to 4 days ago 1.31 growing # day 44 new 1617.0 ratio to 4 days ago 0.49 shrinking # day 48 new 145.4 ratio to 4 days ago 0.09 shrinking # day 52 new 7.8 ratio to 4 days ago 0.05 shrinking # day 56 new 0.4 ratio to 4 days ago 0.05 shrinking # peak day: 40 | peak new cases: 3272 # ever infected: 9794 of 10000 # naive 100-day prediction, no limit: 8.88e+09 cases

For about three weeks the growth ratio stays close to 2.5 every 4 days (2.46 on day 24), just as the simple formula says. Then it starts to slip: 2.26 on day 32, 1.94 on day 36, and 1.31 on day 40. The peak of 3,272 new cases a day comes on day 40, and by day 44 the outbreak is shrinking. At the end 9,794 of the 10,000 people have been infected in this model. The last line shows why the early formula must not be extended: 2.5 raised to the power of 25 generations is about 8.9 billion cases, more than the roughly 8 billion people on Earth (check). Unchecked exponential growth always meets a limit, and the shape of the curve you saw in the boarding school, a climb, a peak and a fall, is the result. The overshoot (nearly everyone infected) is a feature of this crude model, which has no separate recovered group and ignores how long people stay infectious.

Common mistakes
Treating R0 as a fixed constant of the pathogen rather than something that depends on the population and its behaviour; quoting an R0 without saying where it came from (sources differ, and the ranges here are one article’s figures); forgetting that doubling time depends on the generation time as well as R0; using the rule of 70 for fast growth (above about 25% a day it is no longer within 10%); extending an exponential forecast far into the future without a limit; and confusing the reproduction number with a rate or with a count of total cases.
Exercise 1

New cases on days 1 to 6 of an outbreak are 12, 15, 19, 24, 30 and 38 (assumed, invented). Find the day-to-day ratios, the average daily growth, the doubling time and what the rule of 70 says, then find the day on which new cases per day first reach 1,000 if growth never changes, by a loop and by a formula.

📄 View solution
Exercise 2

For R0 values of 1.2, 1.5, 2, 2.5 and 3, find how many generations (and how many days, if a generation is 5 days, assumed) it takes for one case to lead to 1,000 cases in a single generation. Then compare the cases in the third generation for an R0 of 12, 18 and 1.3, the figures quoted for measles and seasonal influenza, and decide how much the uncertainty in R0 matters.

📄 View solution
Exercise 3

Wrap the limited-growth simulation in a function and run it for R0 values of 0.9, 1, 1.5, 2, 3 and 5 in a population of 10,000 (generation time 4 days, assumed). Report the peak day, the peak number of new cases and the share ever infected, and explain what happens when R0 is below 1.

📄 View solution

Quick Reference

  • Exponential growth: the same factor each step; the doubling time is constant
  • Doubling time: t × ln(2) / ln(1 + r/100); the rule of 70 (70 / r) is good for r below about 25%
  • R0: expected number of cases one case generates in a fully susceptible population; above 1 the outbreak can grow, below 1 it dies out
  • By generations: generation g has R0g new cases (while few have been used up)
  • With a fixed generation time T (assumed): doubling time = T × ln(2) / ln(R0)
  • Limit: as susceptibles run out, growth slows, peaks and falls
Coming next
How Epidemics Spread 3 is about the SIR model: susceptible, infected and recovered compartments, differential equations, and stepping them in Python.