Logistic Growth
Grow a population too fast and it overshoots its carrying capacity before settling; grow it gently and it glides up to the same limit.
Level: Beginner
Dynamic Behavior Patterns
Explore common system behaviors: exponential growth, goal-seeking decay, overshoot-and-collapse, and S-curve saturation.
Take the QuizSystem Archetypes
Learn recurring structural patterns like Limits to Growth, Fixes That Fail, and Tragedy of the Commons, plus high-leverage interventions.
Take the QuizOvershoot: when fast growth blows past its limit
A population grows logistically toward the carrying capacity K. With a
gentle growth rate it glides up and settles. Push the rate past a
threshold and each step's correction is so large the population overshoots
K, then rings back down — the same loop, two very different fates.
The knob is growth_rate. Below ~1 the approach is smooth; between 1 and
2 it overshoots once and oscillates back to K; above 2 it never settles.
from tys import probe, progress
Evolve a population with the discrete logistic equation.
def simulate(cfg: dict):
import simpy
env = simpy.Environment()
population = cfg["initial_pop"]
r = cfg["growth_rate"]
K = cfg["carrying_capacity"]
steps = cfg["steps"]
peak = population
done = env.event()
Each step nudges the population by r * P * (1 - P/K).
def run():
nonlocal population, peak
for t in range(steps):
growth = r * population * (1 - population / K) # net change this step
population = max(population + growth, 0) # a population can't go below zero
peak = max(peak, population)
probe("population", env.now, population)
probe("growth", env.now, growth) # turns negative while overshooting
progress(int(100 * (t + 1) / steps))
yield env.timeout(1)
done.succeed({
"final_population": population,
"peak_population": peak,
"overshot": peak > K * 1.001,
})
env.process(run())
env.run(until=done)
return done.value
def requirements():
return {
"external": ["simpy==4.1.1"],
}
# overshoot.yaml — the default, with the growth rate past the overshoot threshold.
# At growth_rate 1.9 the population shoots above the carrying capacity, then
# oscillates back down to it.
initial_pop: 10
growth_rate: 1.9
carrying_capacity: 100
steps: 50
| Metric | Value |
|---|---|
| final_population | 99.95 |
| peak_population | 108.07 |
| overshot | true |
# smooth.yaml — the contrast: a gentle growth rate that glides up to capacity.
# Identical to overshoot.yaml except growth_rate (1.9 -> 0.8): below 1, the
# population approaches K from below and never crosses it.
initial_pop: 10
growth_rate: 0.8
carrying_capacity: 100
steps: 50
| Metric | Value |
|---|---|
| final_population | 100.00 |
| peak_population | 100.00 |
| overshot | false |
- What makes the population overshoot the carrying capacity?
- Each step changes the population by r*population*(1 - population/K). When the growth rate r is large, that correction is bigger than the remaining gap to K, so the population climbs above capacity in a single step before the now-negative growth term pulls it back.
- Where is the tipping point?
- For this discrete model the approach is smooth when growth_rate is below 1, overshoots once and rings back down to K when it is between 1 and 2, and stops settling (sustained oscillation) once it exceeds 2.
- Why does the smooth config never cross the line?
- At growth_rate 0.8 each step closes only part of the gap to K, so the population approaches capacity from below and never exceeds it.
- What is a real-world example of overshoot?
- Populations that reproduce faster than their resources can recover - algal blooms or species introduced to a new habitat - shoot past what the environment can sustain, then crash back toward it.