Source-sink population dynamics

ecology tutorial
R
population ecology
conservation
metapopulations
Source-sink dynamics in R: the BIDE budget, why a sink population persists only on immigration from a source, and what the local growth rate really measures.
Author

Tidy Ecology

Published

2026-07-31

A basic idea in population ecology is that habitat quality varies, but the consequences of that idea are strange enough that they still catch people out. In a good patch, births outnumber deaths and the population would grow on its own. In a poor patch, deaths outnumber births and the population would dwindle to nothing if left alone. The good patch, a source, exports its surplus; the poor patch, a sink, survives only on those exports. Pulliam 1988 The American Naturalist 132(5):652-661 laid this out, and it changes what a population count is worth.

The BIDE budget

A local population changes through four flows: Births, Immigration, Deaths, Emigration. Collapse births and deaths into a local finite growth rate, the factor a population multiplies by each year without any movement. For a simple annual life it is adult survival plus juvenile survival times fecundity.

lam <- function(PA, PJ, b) PA + PJ * b   # adult survival + juvenile survival * fecundity
c(source = lam(0.6, 0.3, 3.0), sink = lam(0.6, 0.3, 0.8))
source   sink 
  1.50   0.84 

The source has a local growth rate above one, 1.50: without any immigration it would grow, so its births more than replace its deaths and the extra individuals must go somewhere. The sink has a local growth rate below one, 0.84: without immigration it shrinks every year. A sink is not a small population or a rare one; it is a population that cannot replace itself.

A sink lives on subsidy

Take a sink and follow it two ways: isolated, and receiving the surplus that a source of 100 breeding adults sends out each year.

l2 <- lam(0.6, 0.3, 0.8); surplus <- 100 * (lam(0.6, 0.3, 3.0) - 1)
sink_alone <- Reduce(function(N, .) N * l2,             1:40, accumulate = TRUE, 80)
sink_subs  <- Reduce(function(N, .) N * l2 + surplus,   1:40, accumulate = TRUE, 80)
c(alone_final = tail(sink_alone, 1), subsidised_final = tail(sink_subs, 1))
     alone_final subsidised_final 
        0.074862       312.282432 

Isolated, the sink falls from 80 toward zero, reaching 0.1 after forty years: local extinction, exactly as a growth rate below one demands. Fed the source’s surplus, the same sink climbs and settles at 312. The inflow of 50 immigrants a year exactly balances the annual shortfall between births and deaths. Cut the source and the sink is gone; the persistence you see is borrowed.

d <- rbind(data.frame(t = 0:years, N = Na, kind = "isolated sink"),
           data.frame(t = 0:years, N = Ns, kind = "sink with source subsidy"))
ggplot(d, aes(t, N, colour = kind)) +
  geom_hline(yintercept = n2_eq, colour = col_ref, linetype = "dotted") +
  geom_line(linewidth = 1) +
  scale_colour_manual(values = c("isolated sink" = col_alone, "sink with source subsidy" = col_sub)) +
  labs(x = "Year", y = "Sink population size", colour = NULL,
       title = "The sink persists only on the source's surplus")
Two trajectories of a sink population; the isolated one declines toward zero, the subsidised one rises and levels off at a stable positive size.
Figure 1: A sink population over forty years. Isolated (ochre) it collapses to local extinction; subsidised by a source’s surplus (green) it rises to a stable level held up entirely by immigration.

How large that subsidised population settles depends on how deep a sink it is. A shallow sink, one whose local growth rate is only just below one, loses very little each year, so the same immigration piles up into a large standing population; a severe sink burns through immigrants fast and stays small.

l2g <- seq(0.3, 0.97, 0.01)
de <- data.frame(l2 = l2g, n2 = surplus / (1 - l2g))
ggplot(de, aes(l2, n2)) +
  geom_line(colour = col_sub, linewidth = 1) +
  geom_hline(yintercept = n1, colour = col_ref, linetype = "dotted") +
  annotate("text", x = 0.32, y = n1 + 18, label = "source size", hjust = 0, colour = col_ref, size = 3.4) +
  labs(x = "Sink's own local growth rate", y = "Equilibrium sink size",
       title = "A shallow sink holds a large subsidised population")
A rising curve of equilibrium sink size against the sink's local growth rate, climbing steeply as the growth rate approaches one.
Figure 2: Equilibrium sink size against the sink’s own local growth rate, for a fixed source surplus. As the sink’s growth rate approaches one, the same immigration supports an ever larger population.

What the local growth rate does and does not tell you

The local growth rate is a statement about self-sufficiency: above one, the habitat can stand alone; below one, it cannot. It is not a statement about size, and this is where intuition fails. A sink can be crowded, holding far more animals than the source that feeds it, because the source keeps pushing its surplus downhill into the sink. It is not a statement about importance either: a sink can be a population trap that drains the source, or a valuable stepping stone, depending on the flows. The next post shows how a sink comes to hold more individuals than its source, which is why counting animals tells you almost nothing about habitat quality, and the checking post shows what you have to measure instead.

References

  • Pulliam HR 1988. The American Naturalist 132(5):652-661 (10.1086/284880).

Newsletter

Get new tutorials by email

New R and QGIS tutorials for ecologists, straight to your inbox. No spam; unsubscribe anytime.

By subscribing you agree to receive these emails and confirm your address once. See the privacy policy.