Herd Immunity

How Epidemics Spread

Chapter 4 · Herd Immunity

How Epidemics Spread 3 ended with a hint: infections peak when the susceptible count falls to N/R0, because from then on each case infects fewer than one other person. Nothing in that argument says how the susceptible count falls. It can fall because people catch the disease and recover, or because they were protected beforehand by vaccination. This chapter follows that second route. The idea is called herd immunity: once enough of a community is immune, the few who are not are protected too, because an infection that meets mostly immune people cannot keep itself going. By the end you will have the threshold formula, you will have checked it by simulation, and you will know why an imperfect vaccine needs more than the threshold coverage and why a good average can hide a dangerous pocket.

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 threshold formulas, the measles figures (R0 of 12 to 18 and a threshold of 92 to 94 per cent) and the history of the term (first used by the American veterinary scientist Daniel Elmer Salmon in 1894 for the disease resistance of well-fed herds of hogs, then for cattle in 1916 and for experimental epidemics in mice in 1923) come from the Wikipedia article on herd immunity, fetched with a tool that often returns truncated pages or model-written summaries; this fetch covered only the first 100,000 of about 166,000 characters, so treat the wording as a paraphrase. Source conflict: the same article gives the measles threshold as 92 to 94 per cent in its table but speaks elsewhere of a threshold “exceeding 95%”, and I could not tell which reflects the better estimate. The reasoning that the threshold is the point where R0 × S/N reaches 1 is the same inference made in How Epidemics Spread 3. Every population size, R0, vaccine effectiveness and coverage in the code is assumed or illustrative, not an estimate for any real place or disease.

The Threshold in One Line

How Epidemics Spread 3 showed that, with a fraction S/N of the population still susceptible, each case infects R0 × S/N people. An outbreak can grow only while that number is above 1. So the infection is held down once R0 × S/N is at or below 1, that is, once the susceptible share is at most 1/R0. Everyone else must be immune, so the herd immunity threshold is the immune share

pc = 1 − 1/R0

The Wikipedia article gives the same formula and says it comes from setting R0 × S = 1, where S is the susceptible proportion. Try some numbers: R0 = 2 gives 1 − 1/2 = 50 per cent; R0 = 4 gives 75 per cent; R0 = 12 gives about 92 per cent; R0 = 18 gives about 94 per cent. The last two match the measles range in the article. Notice how quickly the threshold climbs: the more infectious the disease, the more of the population must be immune, but the share never reaches 100 per cent for any finite R0. A disease with R0 below 1 needs no immunity at all, as How Epidemics Spread 3 found.

Peak of the epidemic (How Epidemics Spread 3)Herd immunity threshold (this chapter)
ConditionR0 × S/N = 1R0 × S/N = 1 (same equation)
When it is metpart-way through a natural epidemicbefore the first case, if enough people are vaccinated
Resultcases start to fall, but many more are still infectedan introduced case does not start an epidemic
Costa large share of the population falls illvaccination; the immune have not been ill

A Name from the Farm

The phrase did not begin with people. According to the article, Daniel Elmer Salmon, an American veterinary scientist, first used “herd immunity” in 1894 about the health and disease resistance of well-fed herds of hogs, and later uses concerned cattle with brucellosis (1916) and experimental epidemics among mice (1923). The article also stresses that the immune system does not distinguish between immunity from a natural infection and immunity from a vaccine. The mathematics does not care either: a person in R is a person in R, and what differs is the price paid to get there. The simulation below makes that price visible.

Vaccinating Before the Outbreak

The code below uses the SIR rules from How Epidemics Spread 3 with R0 = 2.5, so the threshold is 1 − 1/2.5 = 60 per cent. Before the first case arrives, a share of the population is moved straight into R, as if by a vaccine that is perfect and protects completely (assumed; the next sections drop that assumption). Then one infectious person appears in the 10,000 and the model runs until the outbreak ends, with a step of 0.05 days. The table reports the largest number infectious at once, the share of the whole population that caught the disease, and the share of the unvaccinated who did.

# Vaccinating before the outbreak: what fraction ends up infected? All numbers assumed. population = 10_000 gamma = 0.2 # assumed: average infectious period 5 days r0 = 2.5 # assumed, as in the earlier chapters beta = r0 * gamma dt = 0.05 def outbreak(coverage): """Perfect, all-or-nothing vaccine given before the first case (assumed).""" vaccinated = coverage * population S, I, R = population - vaccinated - 1.0, 1.0, vaccinated peak_I, t = I, 0.0 while I >= 0.01 and t < 2000: new_inf = beta * S * I / population * dt new_rec = gamma * I * dt S -= new_inf I += new_inf - new_rec R += new_rec t += dt peak_I = max(peak_I, I) unvaccinated = population - vaccinated return peak_I, (unvaccinated - S) / population, (unvaccinated - S) / unvaccinated print(f"threshold 1 - 1/R0 = {1 - 1 / r0:.0%}") print("coverage peak infectious share of everyone infected share of unvaccinated infected") for coverage in [0.0, 0.2, 0.4, 0.5, 0.6, 0.7, 0.8]: peak, everyone, unvax = outbreak(coverage) print(f"{coverage:7.0%} {peak:16.0f} {everyone:20.1%} {unvax:28.1%}") # threshold 1 - 1/R0 = 60% # coverage peak infectious share of everyone infected share of unvaccinated infected # 0% 2343 89.3% 89.3% # 20% 1231 63.8% 79.7% # 40% 379 35.0% 58.3% # 50% 108 18.6% 37.2% # 60% 1 0.9% 2.2% # 70% 1 0.0% 0.1% # 80% 1 0.0% 0.1%

Three features deserve attention. First, with no vaccination the outbreak infects 89.3 per cent, matching the figure for R0 = 2.5 in How Epidemics Spread 3. Natural herd immunity is reached on the way, when 60 per cent have been infected, yet the epidemic carries on to 89.3 per cent: the cases already infectious keep infecting people after the threshold is passed (an overshoot). Vaccination before the outbreak avoids that overshoot. Second, the benefit is not linear. Vaccinating 20 per cent of the population cuts the peak from 2,343 to 1,231, and 40 per cent cuts it to 379, but at 60 per cent the peak is 1: the single introduced case never grows. Third, look at the last column: at 60 per cent coverage the unvaccinated, who have no protection of their own, are infected 2.2 per cent of the time, against 89.3 per cent with no vaccination. That drop is the “herd” part of herd immunity. The 0.9 and 2.2 per cent at exactly 60 per cent are leftovers from the first case: the outbreak sits on the knife-edge R = 1 and dies slowly. At 70 per cent it has all but vanished.

Imperfect Vaccines

A real vaccine does not protect everyone. If its effectiveness is E (the share of vaccinated people who end up protected) and a share v of the population is vaccinated, the immune share is v × E. Setting that equal to the threshold gives the coverage needed. The article states the result as Vc = (1 − 1/R0) / E, and adds that if E is less than 1 − 1/R0, eliminating the disease is impossible even if the whole population is vaccinated. The next code prints that formula for a few illustrative cases.

# Coverage needed when the vaccine is imperfect: Vc = (1 - 1/R0) / E. # R0 values are illustrative; E is vaccine effectiveness (all assumed here). r0_values = [2, 3, 5, 12, 18] effectiveness = [1.00, 0.95, 0.90, 0.80] print("R0 threshold coverage needed at E = " + " ".join(f"{e:.0%}" for e in effectiveness)) for r0 in r0_values: threshold = 1 - 1 / r0 cells = [] for e in effectiveness: needed = threshold / e cells.append(f"{needed:>4.0%}" if needed <= 1 else "none") print(f"{r0:2d} {threshold:10.1%} " + " ".join(f"{c:>6}" for c in cells)) print("'none' means even 100% coverage cannot push R below 1 (E < 1 - 1/R0).") # R0 threshold coverage needed at E = 100% 95% 90% 80% # 2 50.0% 50% 53% 56% 62% # 3 66.7% 67% 70% 74% 83% # 5 80.0% 80% 84% 89% 100% # 12 91.7% 92% 96% none none # 18 94.4% 94% 99% none none # 'none' means even 100% coverage cannot push R below 1 (E < 1 - 1/R0).

Read across the rows. For R0 = 3 a 95 per cent effective vaccine needs 70 per cent coverage, not 67 per cent. For the high R0 values the demands become severe: with R0 = 12 and E = 95 per cent the coverage needed is 96 per cent, and at E = 90 per cent the threshold of 91.7 per cent is above what the vaccine can deliver, so even vaccinating everybody cannot stop spread by this route. The R0 = 5 row at E = 80 per cent lands on exactly 100 per cent because the threshold (80 per cent) equals the vaccine’s effectiveness: with every single person vaccinated R would be exactly 1, which is no safety margin at all. Real programmes also deal with things this table ignores, such as people who cannot be vaccinated and the time it takes to reach everyone.

Two Ways a Vaccine Can Fail

“80 per cent effective” can mean two quite different things (the two models below are standard textbook cartoons, assumed here rather than taken from the article). In the all-or-nothing picture, 80 per cent of vaccinated people are fully protected and 20 per cent have no protection at all. In the leaky picture, every vaccinated person is still susceptible, but their chance of infection per exposure is cut by 80 per cent. The code compares them at R0 = 4, where the perfect-vaccine threshold is 75 per cent and the coverage needed at E = 80 per cent is (1 − 1/4)/0.8 = 93.75 per cent.

# Two kinds of imperfect vaccine, same average effectiveness (E = 80%, assumed). # all-or-nothing: 80% of vaccinated people are fully protected, 20% not at all # leaky: every vaccinated person has their chance of infection cut by 80% population, gamma, r0, E, dt = 10_000, 0.2, 4.0, 0.8, 0.05 beta = r0 * gamma def all_or_nothing(coverage): protected = coverage * E * population S, I = population - protected - 1.0, 1.0 while I >= 0.01: new_inf = beta * S * I / population * dt S -= new_inf I += new_inf - gamma * I * dt return (population - protected - S) / population def leaky(coverage): Su = (1 - coverage) * population - 1.0 # unvaccinated susceptibles Sv = coverage * population # vaccinated, still partly susceptible I, ever = 1.0, 0.0 while I >= 0.01: force = beta * I / population * dt inf_u = Su * force inf_v = Sv * force * (1 - E) Su -= inf_u Sv -= inf_v I += inf_u + inf_v - gamma * I * dt ever += inf_u + inf_v return ever / population print(f"threshold with perfect vaccine: {1 - 1 / r0:.0%}; coverage needed at E=80%: {(1 - 1 / r0) / E:.1%}") print("coverage all-or-nothing leaky (share of whole population infected)") for coverage in [0.0, 0.5, 0.8, 0.9, 0.9375, 1.0]: print(f"{coverage:8.2%} {all_or_nothing(coverage):15.1%} {leaky(coverage):9.1%}") # threshold with perfect vaccine: 75%; coverage needed at E=80%: 93.8% # coverage all-or-nothing leaky (share of whole population infected) # 0.00% 98.1% 98.1% # 50.00% 52.8% 67.5% # 80.00% 19.5% 33.7% # 90.00% 5.8% 12.5% # 93.75% 0.7% 1.1% # 100.00% 0.0% 0.0%
All-or-nothingLeaky
Vaccinated peoplesome fully protected, some not at allall partly protected
At 50% coverage (R0 = 4, E = 80%)52.8% of the population infected67.5% infected
At 90% coverage5.8% infected12.5% infected
At 93.75% coverage0.7% infected1.1% infected
Hidden dangerthe unprotected look like the unvaccinatedvaccinated people can be infected and may pass it on

Both models bring the outbreak almost to nothing at about the 93.75 per cent the formula predicts, and both give little protection when coverage is low, but at middle coverages the leaky vaccine lets through noticeably more infection (12.5 against 5.8 per cent at 90 per cent coverage). One reason is that every vaccinated person in the leaky model remains a possible host, whereas in the all-or-nothing case the protected people drop out of the chain entirely. My simple leaky model also treats a vaccinated person who catches the disease as equally infectious as anyone else, an assumption that could overstate the damage. The point to keep is that one number, “E = 80 per cent”, does not tell you how a vaccine fails.

When the Average Lies

The formula assumes everyone mixes equally. Real communities do not: children share schools, families share homes, and some neighbourhoods have lower coverage than others. If non-vaccinators cluster together, the average coverage can be above the threshold while the infection finds a pocket in which R is still above 1. The third exercise puts numbers on that. It is also why public-health writing looks at coverage by district and not only the national figure; this is the course’s own point, not a quotation from the article. How Epidemics Spread 7 returns to uneven contact patterns with networks.

Common mistakes
Forgetting the vaccine’s effectiveness and using 1 − 1/R0 as the coverage; reading the threshold as a switch (just above it large outbreaks stop, just below it they are possible but not certain, as How Epidemics Spread 6 will show); assuming the threshold is fixed for a disease (it depends on R0, which depends on behaviour and place); calling the unvaccinated “protected” as if that were their own immunity (it lasts only while others stay immune); averaging coverage over places that do not mix; and treating the simple formula as exact for real populations, which have varying contact patterns, age structure and waning immunity, the subject of How Epidemics Spread 5.
Exercise 1

Take R0 = 4 and a vaccine with E = 90% (assumed, all-or-nothing). Compute the coverage the formula says is needed, then simulate coverages 10 points below it, 5 points below, exactly at it, and 5 points above. Print the peak number infectious and the share of the population infected for each.

📄 View solution
Exercise 2

With R0 = 6 and E = 80% (assumed), show that vaccinating everyone cannot prevent a (smaller) outbreak, by simulating coverages of 50%, 80% and 100%. Then show that a vaccine with effectiveness 5 points above the threshold does stop it at full coverage.

📄 View solution
Exercise 3

Build two communities of 5,000, one with 98% coverage and one with 70% (average 84%), with a perfect vaccine and R0 = 5 (threshold 80%, all assumed). Let each person have a share of contacts inside their own community of 50%, 80% and 95%, seed one case in each community in turn, and report how many people each community loses (not counting the first case) and the share of everyone infected.

📄 View solution

Quick Reference

  • Herd immunity threshold: pc = 1 − 1/R0 (immune share needed, from R0 × S/N = 1)
  • With vaccine effectiveness E: coverage needed = (1 − 1/R0) / E; impossible if E < 1 − 1/R0
  • Examples: R0 2, 3, 5, 12, 18 give thresholds 50%, 67%, 80%, 92%, 94% (measles R0 is 12 to 18 per the article)
  • Overshoot: a natural epidemic passes the threshold; with R0 = 2.5 it infected 89.3% against a 60% threshold
  • Indirect protection: at the threshold the unvaccinated are infected far less often
  • Failure modes: all-or-nothing and leaky vaccines can have the same E but different outcomes
  • Assumes: equal mixing; averages can hide low-coverage pockets
Coming next
How Epidemics Spread 5 is about adding realism: latent periods (SEIR), deaths, waning immunity, births, endemic diseases.