Population Biology · ODEs · Chaos
📅 March 2026 ⏱ ≈ 10 min read 🎯 Beginner–Intermediate

Lotka–Volterra Equations — Predator–Prey Population Cycles

First formulated independently by Alfred Lotka (1920) and Vito Volterra (1926), the Lotka–Volterra equations model the cyclical oscillation of two interacting populations — a prey species and its predator. The model reveals how a simple two-equation system can produce perpetual, self-sustaining cycles without any external forcing.

1. The two-species model and assumptions

The classic Lotka–Volterra system involves two populations interacting in a predator–prey relationship:

🐇
Prey (x)

Rabbits, fish, or any species that is hunted. Grows without limit in the absence of predators.

🦊
Predator (y)

Foxes, sharks, or any predator. Depends entirely on prey for food; starves without it.

The model rests on four key assumptions:

Historical context: Volterra developed the equations after Italian biologist Umberto d'Ancona asked him why Adriatic shark catches had increased during World War I (when fishing was reduced). Volterra's model predicted this counter-intuitive result: reduced fishing pressure benefits both prey and predator, but predators benefit proportionally more.

2. System of ODEs — derivation

Let x(t) be the prey population and y(t) be the predator population. The four parameters are:

α — prey birth rate

Per-capita exponential growth rate of prey in the absence of predators. Typical: 0.5–2.0 yr⁻¹.

β — predation rate

Rate at which predators consume prey per unit of each population. Controls encounter efficiency. Typical: 0.01–0.1.

δ — predator growth rate

Conversion efficiency of consumed prey into predator offspring. Often δ < β. Typical: 0.001–0.02.

γ — predator death rate

Per-capita death rate of predators in the absence of prey. Typical: 0.2–1.0 yr⁻¹.

The governing equations are:

dx/dt = α·x − β·x·y ← prey: grows alone, shrinks when eaten
dy/dt = −γ·y + δ·x·y ← predator: dies alone, grows when fed

Term breakdown:
α·x — exponential prey birth (logistic if replaced by α·x·(1 − x/K))
−β·x·y — prey eaten: each predator-prey encounter removes β prey/time
−γ·y — predator starvation in absence of prey
δ·x·y — predator reproduction from consuming prey (δ = conversion factor)

The mass-action term β·x·y is the key nonlinearity. Without it the equations decouple into two independent exponentials. With it, the populations are coupled and oscillate indefinitely.

Dimensional analysis: With x and y in individuals and t in years, [α] = yr⁻¹, [β] = (ind·yr)⁻¹, [γ] = yr⁻¹, [δ] = (ind·yr)⁻¹. The product β·x·y must have units ind/yr to match dx/dt — check your parameters are consistent.

3. Equilibria and stability analysis

Setting dx/dt = dy/dt = 0 reveals two fixed points:

Trivial equilibrium E₀ = (0, 0):
Both species extinct. Unstable saddle point.

Non-trivial equilibrium E* = (x*, y*):
x* = γ/δ ← prey level that keeps predators constant
y* = α/β ← predator level that keeps prey constant

Set dx/dt = 0: x(α − β·y) = 0 → y = α/β (for x ≠ 0)
Set dy/dt = 0: y(−γ + δ·x) = 0 → x = γ/δ (for y ≠ 0)

Linearisation at E*: The Jacobian matrix evaluated at the non-trivial equilibrium has purely imaginary eigenvalues:

J(E*) = [ [0, −β·x*],
        [δ·y*, 0] ]

eigenvalues: λ = ± i·√(α·γ) ← purely imaginary → centre

Period of small oscillations: T ≈ 2π / √(α·γ)

Purely imaginary eigenvalues indicate a centre in the linearised system. For the fully nonlinear Lotka–Volterra equations, this centre is neutrally stable: perturbations do not grow or decay — they orbit the equilibrium forever on a closed trajectory whose shape depends on initial conditions.

Structural instability: The neutral stability is a mathematical fragility. Any biological realism — intraspecific competition, non-linear functional responses, time lags — breaks the perfect cycles. Real ecosystems are generally asymptotically stable (populations return to equilibrium after perturbation), but the Lotka–Volterra idealization remains the starting point for all population dynamics models.

4. Phase portrait and conservation law

The Lotka–Volterra system has a remarkable first integral (conserved quantity), analogous to energy conservation in mechanics:

V(x, y) = δ·x − γ·ln(x) + β·y − α·ln(y) = constant

Verification: dV/dt = (δ − γ/x)·(dx/dt) + (β − α/y)·(dy/dt)
= (δ − γ/x)·(αx − βxy) + (β − α/y)·(−γy + δxy)
= ... = 0 ✓ (cancellation is exact)

Each closed orbit in the phase plane (x, y) corresponds to a constant value of V. The orbit closest to the equilibrium E* has the smallest V; the amplitude of oscillation grows as V increases.

In the phase portrait you will observe:

Why does prey peak before predators? When prey is abundant, predators have plenty of food and start reproducing quickly. As predators multiply, they over-hunt, crashing prey. With little food, predators begin to starve and die. With fewer predators, prey recover — and the cycle begins again. This quarter-period lag is observed in real Canadian lynx–hare data spanning over a century.

5. Parameter effects — what changes the cycle?

Each parameter changes a specific aspect of the oscillation:

Increase α (faster prey growth)

Higher equilibrium predator count y* = α/β. Faster oscillations (period T ∝ 1/√α). Prey recover faster after predation.

Increase β (higher predation efficiency)

Lower equilibrium prey x* = γ/δ remains unchanged; prey equilibrium y* = α/β decreases. Larger amplitude swings.

Increase γ (faster predator death)

Predators collapse faster without food. Lower equilibrium predator density. Longer period T between peaks.

Increase δ (better food conversion)

Each kill produces more offspring. Lower equilibrium prey count x* = γ/δ. Oscillations become more tightly coupled.

Adding a logistic cap for prey (αx(1−x/K)) transforms the neutral centre into a stable spiral: trajectories spiral inward, reaching a unique limit cycle (if the equilibrium lies inside the feasible region) or a stable point (if K is small).

6. JavaScript implementation with RK4

The 4th-order Runge–Kutta method (RK4) gives excellent accuracy at reasonable cost. Each step requires four evaluations of the right-hand side function:

// Lotka–Volterra right-hand side
function lv(x, y, α, β, γ, δ) {
  return {
    dxdt:  α * x - β * x * y,
    dydt: -γ * y + δ * x * y,
  };
}

// RK4 single step
function rk4Step(x, y, h, α, β, γ, δ) {
  const k1 = lv(x,            y,            α, β, γ, δ);
  const k2 = lv(x + h/2*k1.dxdt, y + h/2*k1.dydt, α, β, γ, δ);
  const k3 = lv(x + h/2*k2.dxdt, y + h/2*k2.dydt, α, β, γ, δ);
  const k4 = lv(x + h*k3.dxdt,   y + h*k3.dydt,   α, β, γ, δ);

  return {
    x: x + (h/6) * (k1.dxdt + 2*k2.dxdt + 2*k3.dxdt + k4.dxdt),
    y: y + (h/6) * (k1.dydt + 2*k2.dydt + 2*k3.dydt + k4.dydt),
  };
}

// Simulate for T years with step h
function simulate(x0, y0, α, β, γ, δ, T, h = 0.01) {
  const history = [{ t: 0, x: x0, y: y0 }];
  let { x, y } = { x: x0, y: y0 };

  for (let t = h; t <= T; t += h) {
    ({ x, y } = rk4Step(x, y, h, α, β, γ, δ));
    // Guard against numerical blow-up near extinction
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    history.push({ t, x, y });
  }
  return history;
}

// Example: classic parameter set
const α = 1.0;  // prey birth rate
const β = 0.1;  // predation rate
const γ = 0.4;  // predator death rate
const δ = 0.02; // predator conversion efficiency

const data = simulate(10, 5, α, β, γ, δ, 50);
// data[i].x → prey population at time data[i].t
// data[i].y → predator population at time data[i].t

Rendering the phase portrait

// Draw (x, y) trajectory on a 2D canvas
function drawPhasePortrait(ctx, data, W, H) {
  const maxX = Math.max(...data.map(p => p.x)) * 1.1;
  const maxY = Math.max(...data.map(p => p.y)) * 1.1;

  const cx = p => (p.x / maxX) * W;
  const cy = p => H - (p.y / maxY) * H;

  ctx.beginPath();
  data.forEach((p, i) => {
    i === 0
      ? ctx.moveTo(cx(p), cy(p))
      : ctx.lineTo(cx(p), cy(p));
  });
  ctx.strokeStyle = '#22c55e';
  ctx.lineWidth = 1.5;
  ctx.stroke();

  // Mark equilibrium E* = (γ/δ, α/β)
  const eq = { x: γ/δ, y: α/β };
  ctx.beginPath();
  ctx.arc(cx(eq), cy(eq), 5, 0, Math.PI * 2);
  ctx.fillStyle = '#f59e0b';
  ctx.fill();
}

🐺 Live Predator–Prey Simulation

Adjust α, β, γ, δ sliders and watch the population cycles in real time. Switch between time-series and phase-portrait view.

Open Simulation →

7. Extensions: competition, three species, stochasticity

Logistic prey (Rosenzweig–MacArthur model)

Replacing the linear prey term with a logistic cap introduces a carrying capacity K:

dx/dt = α·x·(1 − x/K) − β·x·y
dy/dt = −γ·y + δ·x·y

Now the equilibrium can be a stable spiral or a limit cycle
depending on K vs (γ/δ). A "Hopf bifurcation" occurs at K = γ/(2δ).

This extension removes the structural instability and gives realistic closed limit cycles (Hopf attractors) for intermediate K values.

Competitive Lotka–Volterra (two prey species)

Two competing species sharing a resource:

dx₁/dt = r₁·x₁·(1 − (x₁ + α₁₂·x₂)/K₁)
dx₂/dt = r₂·x₂·(1 − (x₂ + α₂₁·x₁)/K₂)

αᵢⱼ = effect of species j on species i (competition coefficient)
Coexistence: α₁₂ < K₁/K₂ AND α₂₁ < K₂/K₁ (competitive coexistence)

Three-species food chains

Adding a top predator z (which eats y) creates a 3D ODE system. The trophic cascade can exhibit chaotic dynamics — the Hastings–Powell (1991) model showed that simple food chains can produce chaos without any stochastic driving, purely from the nonlinear interactions.

Stochastic extensions

Deterministic models give ensemble-average behaviour. Real populations are finite and discrete, making stochasticity essential for small populations:

Canadian Lynx and Snowshoe Hare: The Hudson Bay Company's 200-year pelt records (1845–1935) are the most famous test of Lotka–Volterra predictions. Both species show 9–11 year oscillations with prey leading predators by ~1–2 years. Modern analysis shows the hare cycle is partially self-driven (vegetation over-grazing) and partially predator-driven — a combination that neither pure model captures alone.