The Monocentric City

The Alonso-Muth-Mills model

All jobs are at the CBD (central business district). Workers choose where to live, trading off commuting costs against housing costs. In equilibrium, no one wants to move — which means land rent must decline with distance from the CBD to compensate for longer commutes. This declining curve is the rent gradient.

The rent gradient

In the simplest version, rent declines linearly with distance:

\[R(d) = R_{CBD} - t \cdot d\]

where \(d\) is distance from the CBD, \(t\) is the commuting cost per mile, and \(R_{CBD}\) is rent at the center. The city edge \(d^*\) is where rent falls to the agricultural rent \(R_a\):

\[d^* = \frac{R_{CBD} - R_a}{t}\]

Beyond \(d^*\), land is worth more in agriculture than in housing, so the city stops.

Comparative statics

  • Higher transport cost \(t\): steeper gradient, smaller city. Workers pay more per mile of commute, so they bid less for distant land. The city shrinks inward.
  • Higher income/wages: ambiguous. Higher income means more demand for space (flatter gradient, bigger city), but also higher time cost of commuting (steeper gradient, smaller city). Empirically, the space effect dominates — richer cities are more spread out.
  • Higher population: more people compete for land near the CBD, pushing \(R_{CBD}\) up. The city expands outward until the marginal resident is indifferent between the edge and the center.

The Model View. In the simulation below, we use a simplified version where \(R_{CBD}\) is determined by population pressure and income. The key intuition — rent declines with distance, city size adjusts to fit everyone — holds in much richer models.

#| standalone: true
#| viewerHeight: 700

library(shiny)

ui <- fluidPage(
  tags$head(tags$style(HTML("
    .stats-box {
      background: #f0f4f8; border-radius: 6px; padding: 14px;
      margin-top: 12px; font-size: 14px; line-height: 1.9;
    }
    .stats-box b { color: #2c3e50; }
    .good { color: #27ae60; font-weight: bold; }
    .bad  { color: #e74c3c; font-weight: bold; }
    .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("transport", "Transport cost ($/mile):",
                  min = 50, max = 500, value = 200, step = 25),

      sliderInput("income", "Wage/income ($1000s):",
                  min = 20, max = 120, value = 60, step = 5),

      sliderInput("ag_rent", "Agricultural rent ($/acre):",
                  min = 50, max = 500, value = 100, step = 25),

      sliderInput("pop", "Population (thousands):",
                  min = 50, max = 1000, value = 300, step = 25),

      actionButton("go", "Update city", class = "btn-primary", width = "100%"),

      uiOutput("info")
    ),

    mainPanel(
      width = 9,
      fluidRow(
        column(4, plotOutput("rent_plot", height = "400px")),
        column(4, plotOutput("density_plot", height = "400px")),
        column(4, plotOutput("city_plot", height = "400px"))
      )
    )
  )
)

server <- function(input, output, session) {

  city <- reactive({
    input$go
    t_cost  <- input$transport
    income  <- input$income * 1000
    r_ag    <- input$ag_rent
    pop     <- input$pop * 1000

    # CBD rent determined by population pressure and income
    # Higher pop and income push CBD rent up
    r_cbd <- r_ag + sqrt(pop * income * t_cost) * 0.01

    # City radius: where rent = agricultural rent
    d_star <- (r_cbd - r_ag) / t_cost

    # Ensure reasonable bounds
    d_star <- max(d_star, 0.1)

    # Distance vector
    d <- seq(0, d_star * 1.3, length.out = 200)

    # Rent gradient
    rent <- pmax(r_cbd - t_cost * d, r_ag)

    # Population density declines with distance (proportional to rent)
    density_cbd <- pop / (pi * d_star^2) * 2
    density <- density_cbd * pmax(1 - d / d_star, 0)

    # Total area
    area <- pi * d_star^2

    # Average rent (within city)
    avg_rent <- (r_cbd + r_ag) / 2

    list(d = d, rent = rent, density = density,
         d_star = d_star, r_cbd = r_cbd, r_ag = r_ag,
         area = area, avg_rent = avg_rent, t_cost = t_cost,
         density_cbd = density_cbd)
  })

  output$rent_plot <- renderPlot({
    c <- city()
    par(mar = c(4.5, 4.5, 3, 1))
    plot(c$d, c$rent, type = "l", lwd = 3, col = "#2c3e50",
         xlab = "Distance from CBD (miles)",
         ylab = "Rent ($/acre)",
         main = "Rent Gradient")
    abline(h = c$r_ag, lty = 2, col = "#e74c3c", lwd = 2)
    abline(v = c$d_star, lty = 3, col = "#7f8c8d", lwd = 1.5)
    text(c$d_star, c$r_cbd * 0.9,
         paste0("City edge\nd* = ", round(c$d_star, 1), " mi"),
         pos = 4, cex = 0.8, col = "#7f8c8d")
    legend("topright", bty = "n", cex = 0.85,
           legend = c("Rent gradient", "Agricultural rent"),
           col = c("#2c3e50", "#e74c3c"), lwd = c(3, 2), lty = c(1, 2))
  })

  output$density_plot <- renderPlot({
    c <- city()
    par(mar = c(4.5, 4.5, 3, 1))
    d_city <- c$d[c$d <= c$d_star]
    dens_city <- c$density[seq_along(d_city)]
    plot(d_city, dens_city, type = "l", lwd = 3, col = "#3498db",
         xlab = "Distance from CBD (miles)",
         ylab = "Population density (per sq mi)",
         main = "Population Density")
    polygon(c(d_city, rev(d_city)),
            c(dens_city, rep(0, length(d_city))),
            col = adjustcolor("#3498db", 0.2), border = NA)
    abline(v = c$d_star, lty = 3, col = "#7f8c8d", lwd = 1.5)
  })

  output$city_plot <- renderPlot({
    c <- city()
    par(mar = c(1, 1, 3, 1))
    theta <- seq(0, 2 * pi, length.out = 100)
    r_max <- c$d_star * 1.3

    plot(NULL, xlim = c(-r_max, r_max), ylim = c(-r_max, r_max),
         xlab = "", ylab = "", main = "City Footprint",
         asp = 1, axes = FALSE)

    # Fill city area
    polygon(c$d_star * cos(theta), c$d_star * sin(theta),
            col = adjustcolor("#e74c3c", 0.15), border = "#e74c3c", lwd = 2)

    # CBD point
    points(0, 0, pch = 19, cex = 2, col = "#2c3e50")
    text(0, 0, "CBD", pos = 3, cex = 0.9, col = "#2c3e50", font = 2)

    # Radius line
    segments(0, 0, c$d_star, 0, lwd = 2, col = "#e74c3c", lty = 2)
    text(c$d_star / 2, -c$d_star * 0.12,
         paste0(round(c$d_star, 1), " mi"), cex = 0.85, col = "#e74c3c")
  })

  output$info <- renderUI({
    c <- city()
    tags$div(class = "info-box",
      HTML(paste0(
        "<b>City radius:</b> ", round(c$d_star, 1), " miles<br>",
        "<b>CBD rent:</b> $", format(round(c$r_cbd), big.mark = ","), "/acre<br>",
        "<b>Avg rent:</b> $", format(round(c$avg_rent), big.mark = ","), "/acre<br>",
        "<b>City area:</b> ", format(round(c$area, 1), big.mark = ","), " sq mi"
      ))
    )
  })
}

shinyApp(ui, server)

Things to try

  • Raise transport cost: the gradient steepens and the city shrinks. People can’t afford to live far out when commuting is expensive.
  • Lower transport cost to $50: the city sprawls outward. This is what happened when cars replaced streetcars — lower \(t\) meant flatter gradients and suburban growth.
  • Increase population: CBD rent rises as more people compete for central locations. The city expands to accommodate them.
  • Raise income: the city grows because workers demand more space. This is consistent with the American pattern of higher-income suburbs.

Why cities change shape

The monocentric model explains the great reshaping of American cities in the 20th century. Before cars, commuting costs were high (walking, horse-drawn streetcars). Rent gradients were steep, and cities were compact and dense. Think of 1900 Manhattan or Chicago.

The automobile slashed \(t\). The gradient flattened. Suddenly, land 20 miles from the CBD was accessible and cheap. Suburbs exploded. Between 1950 and 2000, the share of Americans living in suburbs roughly doubled.

Rising incomes reinforced the pattern. Higher-income households demand more space (larger lots, bigger houses). Since space is cheaper at the edge, they moved outward — the classic “drive until you qualify” pattern. The poor were left in the center, not because the center was cheap (it wasn’t — small apartments, high per-square-foot costs) but because they couldn’t afford the commute.


Did you know?

  • William Alonso published Location and Land Use in 1964, formalizing the idea that urban land rents reflect a tradeoff between commuting costs and access. He was building on Johann Heinrich von Thunen’s 1826 model of agricultural rings around a market town — the same logic, just applied to cities instead of farms.
  • The monocentric model predicts that rent gradients should flatten as transport costs fall. Glaeser and Kahn (2004) confirmed this empirically: American cities became dramatically less dense as car ownership rose.
  • LA vs NYC illustrate different gradients. NYC has a very steep gradient (extremely expensive center, rapid price drop with distance), reflecting its transit-oriented history. LA has a much flatter gradient, consistent with its car-oriented development. The monocentric model explains both as consequences of different transport cost structures.