Transportation and Congestion
Why traffic is an economic problem
When you drive onto a busy road, you slow down everyone else a little. You pay for your own time, but not for theirs. Since no one pays the full cost of their trip, people drive too much. The result is congestion. It is a classic externality.
Travel time rises with traffic
On an empty road, travel time is short. As traffic gets close to the road’s capacity, travel time climbs fast. The last few cars add much more delay than the first few.
The fix: a congestion toll
A toll equal to the gap between social and private cost, at the efficient level of traffic, makes each driver pay for the delay they cause. This idea comes from William Vickrey in the 1950s and 1960s. Some drivers switch times, routes, or modes. The ones who stay get a faster trip.
Why new lanes do not fix traffic
Duranton and Turner (2011) found that vehicle miles in US cities rise about one for one with new highway lanes. They call this the fundamental law of road congestion. New capacity lowers travel time for a while, but new trips fill the road again.
#| standalone: true
#| viewerHeight: 640
library(shiny)
ui <- fluidPage(
tags$head(tags$style(HTML("
.info-box {
background: #eaf2f8; border-radius: 6px; padding: 14px;
margin-top: 12px; font-size: 13px; line-height: 1.8;
}
.info-box b { color: #2c3e50; }
"))),
sidebarLayout(
sidebarPanel(
width = 3,
sliderInput("demand", "Demand for trips (value of first trip, $):",
min = 20, max = 60, value = 40, step = 1),
sliderInput("cap", "Road capacity (cars per hour):",
min = 1000, max = 4000, value = 2000, step = 250),
sliderInput("vot", "Value of time ($ per hour):",
min = 10, max = 50, value = 25, step = 5),
sliderInput("toll", "Toll ($ per trip):",
min = 0, max = 20, value = 0, step = 0.5),
uiOutput("info")
),
mainPanel(
width = 9,
plotOutput("cost_plot", height = "500px")
)
)
)
server <- function(input, output, session) {
t0 <- 0.5 # free-flow trip time, hours
slope <- 0.008 # demand falls $0.008 per extra car per hour
model <- reactive({
a <- input$demand; K <- input$cap; v <- input$vot; tau <- input$toll
tt <- function(V) t0 * (1 + 0.15 * (V / K)^4)
dtt <- function(V) t0 * 0.6 * V^3 / K^4
pc <- function(V) v * tt(V)
msc <- function(V) pc(V) + V * v * dtt(V)
dem <- function(V) a - slope * V
vmax <- a / slope
solve_at <- function(f) {
g <- function(V) dem(V) - f(V)
if (g(0) <= 0) return(0)
uniroot(g, c(0, vmax))$root
}
v_eq <- solve_at(function(V) pc(V) + tau)
v_opt <- solve_at(msc)
toll_opt <- msc(v_opt) - pc(v_opt)
# deadweight loss of the untolled equilibrium
v_free <- solve_at(pc)
dwl <- if (v_free > v_opt) integrate(function(V) msc(V) - dem(V), v_opt, v_free)$value else 0
list(tt = tt, pc = pc, msc = msc, dem = dem, vmax = vmax,
v_eq = v_eq, v_opt = v_opt, toll_opt = toll_opt, dwl = dwl, tau = tau)
})
output$cost_plot <- renderPlot({
m <- model()
V <- seq(0, m$vmax, length.out = 400)
par(mar = c(4.5, 4.5, 3, 1))
ymax <- max(m$dem(0), 1.3 * m$msc(max(m$v_eq, m$v_opt)))
plot(V, m$dem(V), type = "l", lwd = 3, col = "#2c3e50",
ylim = c(0, ymax), xlab = "Cars per hour",
ylab = "Dollars per trip", main = "Demand, private cost, and social cost")
lines(V, m$pc(V), lwd = 3, col = "#3498db")
lines(V, m$msc(V), lwd = 3, col = "#e74c3c")
if (m$tau > 0) lines(V, m$pc(V) + m$tau, lwd = 2, lty = 2, col = "#3498db")
abline(v = m$v_eq, lty = 3, col = "#3498db", lwd = 2)
abline(v = m$v_opt, lty = 3, col = "#27ae60", lwd = 2)
legend("topright", bty = "n", cex = 0.9,
legend = c("Demand (value of a trip)", "Private cost",
"Social cost", "Private cost + toll",
"Traffic with current toll", "Efficient traffic"),
col = c("#2c3e50", "#3498db", "#e74c3c", "#3498db", "#3498db", "#27ae60"),
lwd = c(3, 3, 3, 2, 2, 2), lty = c(1, 1, 1, 2, 3, 3))
})
output$info <- renderUI({
m <- model()
tags$div(class = "info-box",
HTML(paste0(
"<b>Traffic now:</b> ", format(round(m$v_eq), big.mark = ","), " cars/hour<br>",
"<b>Trip time now:</b> ", round(60 * m$tt(m$v_eq)), " minutes<br>",
"<b>Efficient traffic:</b> ", format(round(m$v_opt), big.mark = ","), " cars/hour<br>",
"<b>Efficient toll:</b> $", sprintf("%.2f", m$toll_opt), "<br>",
"<b>Loss with no toll:</b> $", format(round(m$dwl), big.mark = ","), " per hour"
))
)
})
}
shinyApp(ui, server)
Things to try
- Set the toll equal to the efficient toll: the blue dotted line moves onto the green one. Traffic falls to the efficient level.
- Double the capacity with no toll: trip time falls, but traffic rises. Then raise demand a little, as a growing city would. Trip time creeps back up. This is the fundamental law in action.
- Raise the value of time: the gap between private and social cost grows. Congestion is more costly in rich, busy cities.
- Lower demand: at low traffic, the efficient toll is close to zero. Tolls matter only on crowded roads.
Congestion pricing in practice
Singapore started charging drivers to enter its center in 1975. London followed in 2003 and Stockholm in 2006. New York started its congestion charge for lower Manhattan in January 2025. In each case, traffic in the charged zone fell.
Tolls are unpopular before they start and more popular after. People see faster trips and better bus service. The fairness question matters too. Low-income drivers pay a larger share of their income, but many low-income workers ride transit, and toll revenue can fund it.
Akbar, Couture, Duranton and Storeygard (2023) add a twist. In Indian cities, most of the gap in travel speed between cities comes from slow roads even when traffic is light, not from congestion. Better roads and street design can matter more than tolls.
Mass transit
Buses and trains are the other answer to congestion. Each rider on a full train takes a car off the road.
Transit has a special cost structure. Building a rail line is very expensive, but carrying one more rider costs almost nothing. So transit works best where many people travel the same way: dense cities with a busy downtown.
Transit also gets better as more people use it. More riders justify more frequent service, which means shorter waits for everyone. Economists call this the Mohring effect. It is one reason most cities price transit below cost.
The catch is that most US metro areas are spread out, with jobs in many places. That makes it hard for transit to win riders from cars. A congestion toll helps transit too, since it makes driving downtown cost more.
Connections
- The Monocentric City: lower commuting costs flatten the rent gradient and spread the city out.
- Why Cities Exist: congestion is one of the costs that limits city size.
- Place-Based Policy: new roads and transit lines are investments in particular places.
Did you know?
- William Vickrey won the Nobel Prize in Economics in 1996. He died three days after it was announced.
- The travel-time curve in the simulation comes from the US Bureau of Public Roads in 1964. Planners still call it the BPR function.
- Traffic jams can form with no accident at all. When roads are close to capacity, one driver braking hard can send a wave of stopped cars backward for miles.
Key papers
- Vickrey, W. S. (1963). Pricing in Urban and Suburban Transport. American Economic Review 53(2). The case for congestion pricing.
- Duranton, G. and Turner, M. A. (2011). The Fundamental Law of Road Congestion: Evidence from US Cities. American Economic Review 101(6). New lanes bring new traffic.
- Akbar, P., Couture, V., Duranton, G. and Storeygard, A. (2023). Mobility and Congestion in Urban India. American Economic Review 113(4). Slow roads matter more than traffic.