---
title: "The Trolley Problem as a game under moral uncertainty"
description: "Formalize the Trolley Problem and its variants as decision-theoretic games under moral uncertainty in R, comparing utilitarian, deontological, and contractualist frameworks through expected moral value calculations."
author: "Raban Heller"
date: 2026-05-08
date-modified: 2026-05-08
categories:
- ethics-and-game-theory
- moral-uncertainty
- trolley-problem
- decision-theory
keywords: ["trolley problem", "moral uncertainty", "ethics", "game theory", "utilitarianism", "deontology", "contractualism"]
labels: ["ethics", "decision-under-uncertainty"]
tier: 1
bibliography: ../../../references.bib
vgwort: "TODO_VGWORT_ethics-and-game-theory_trolley-problem-as-game"
image: thumbnail.png
image-alt: "Decision tree for the Trolley Problem showing payoffs under utilitarian and deontological frameworks"
citation:
type: webpage
url: https://r-heller.github.io/equilibria/tutorials/ethics-and-game-theory/trolley-problem-as-game/
license: "CC BY-SA 4.0"
draft: false
has_static_fig: true
has_interactive_fig: true
has_shiny_app: false
---
```{r}
#| label: setup
#| include: false
library(ggplot2)
library(dplyr)
library(tidyr)
library(plotly)
okabe_ito <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442",
"#0072B2", "#D55E00", "#CC79A7", "#999999")
theme_publication <- function(base_size = 12) {
theme_minimal(base_size = base_size) +
theme(
plot.title = element_text(size = base_size * 1.2, face = "bold"),
plot.subtitle = element_text(size = base_size * 0.9, color = "grey40"),
axis.line = element_line(color = "grey30", linewidth = 0.3),
panel.grid.minor = element_blank(),
legend.position = "bottom",
plot.margin = margin(10, 10, 10, 10)
)
}
```
## Introduction & motivation
Philippa @foot_1967 introduced the Trolley Problem as a thought experiment in moral philosophy: a runaway trolley is heading toward five people tied to the tracks; you can divert it onto a side track where it will kill one person instead. Most people say diverting is permissible. But in @thomson_1985's Footbridge variant — where you must push a large man off a bridge to stop the trolley — most people say it is not, even though the outcome is identical (one dies to save five). This pattern is puzzling because it suggests our moral intuitions are not governed by a single consistent principle. From a game-theoretic perspective, the Trolley Problem is a single-player decision under **moral uncertainty**: the decision-maker does not know which moral theory is correct but must act anyway. We can formalize this by treating moral theories as "states of the world" with associated probability weights (credences), defining payoffs under each theory for each action, and computing the expected moral value — exactly analogous to Bayesian decision theory. This approach, pioneered by philosophers like MacAskill and Ord, treats ethical dilemmas as decision problems and applies the same mathematical machinery used throughout game theory. This tutorial builds the formal model in R, computes expected moral values for the classic and Footbridge variants under varying credences, and visualizes how the optimal decision shifts as one's moral credences change.
## Mathematical formulation
**Decision-maker**: A single agent choosing between actions $a \in \{D, N\}$ (Divert or Not-divert in the classic case; Push or Not-push in the Footbridge variant).
**Moral theories** (states of the world):
- **Utilitarianism** ($U$): Maximize total welfare. Payoff = negative lives lost.
- **Deontology** ($K$): Moral value depends on whether the act involves using a person as a means. Killing by diversion incurs a small duty-violation penalty; killing by pushing incurs a large one.
- **Contractualism** ($C$): Evaluate by asking which principle no one could reasonably reject. Broadly aligns with protecting individual rights.
Let $w_U, w_K, w_C$ be the agent's credences (summing to 1). For action $a$ and theory $\theta$, let $V(\theta, a)$ be the moral value. The **expected moral value** is:
$$
\text{EMV}(a) = w_U \cdot V(U, a) + w_K \cdot V(K, a) + w_C \cdot V(C, a)
$$
The agent chooses the action maximizing EMV.
## R implementation
```{r}
#| label: moral-payoffs
# Define payoff matrices for Classic Trolley and Footbridge variants
# Values on a -10 to +10 scale (normalised moral value)
classic_payoffs <- tribble(
~theory, ~divert, ~not_divert,
"Utilitarian", 4, -4, # save 4 net lives = good
"Deontological", -1, 0, # diverting kills 1 actively (small violation) vs inaction
"Contractualist", 2, -2 # 5 people can reasonably reject not-diverting
)
footbridge_payoffs <- tribble(
~theory, ~push, ~not_push,
"Utilitarian", 4, -4, # same net lives saved
"Deontological", -8, 0, # pushing person as means: severe duty violation
"Contractualist", -3, -2 # pushed person can reasonably reject being used
)
cat("Classic Trolley payoffs:\n")
print(classic_payoffs)
cat("\nFootbridge payoffs:\n")
print(footbridge_payoffs)
```
```{r}
#| label: emv-computation
# Expected moral value as function of utilitarian credence w_U
# (split remaining credence equally between K and C)
compute_emv <- function(w_u, payoffs, action_col) {
w_k <- (1 - w_u) / 2
w_c <- (1 - w_u) / 2
weights <- c(w_u, w_k, w_c)
sum(weights * payoffs[[action_col]])
}
w_seq <- seq(0, 1, by = 0.01)
emv_classic <- tibble(w_u = w_seq) |>
rowwise() |>
mutate(
EMV_Divert = compute_emv(w_u, classic_payoffs, "divert"),
EMV_NotDivert = compute_emv(w_u, classic_payoffs, "not_divert"),
optimal = ifelse(EMV_Divert > EMV_NotDivert, "Divert", "Not Divert")
) |> ungroup()
emv_footbridge <- tibble(w_u = w_seq) |>
rowwise() |>
mutate(
EMV_Push = compute_emv(w_u, footbridge_payoffs, "push"),
EMV_NotPush = compute_emv(w_u, footbridge_payoffs, "not_push"),
optimal = ifelse(EMV_Push > EMV_NotPush, "Push", "Not Push")
) |> ungroup()
# Find crossover points
cross_classic <- emv_classic |>
mutate(diff = EMV_Divert - EMV_NotDivert) |>
filter(abs(diff) == min(abs(diff))) |>
pull(w_u)
cross_foot <- emv_footbridge |>
mutate(diff = EMV_Push - EMV_NotPush) |>
filter(abs(diff) == min(abs(diff))) |>
pull(w_u)
cat(sprintf("Classic: Divert optimal when w_U > %.2f\n", cross_classic[1]))
cat(sprintf("Footbridge: Push optimal when w_U > %.2f\n", cross_foot[1]))
```
## Static publication-ready figure
```{r}
#| label: fig-trolley-emv
#| fig-cap: "Figure 1. Expected moral value for the Classic Trolley (left) and Footbridge (right) variants as a function of utilitarian credence. In the Classic case, Divert is optimal for nearly all credence levels — even modest utilitarian weight suffices. In the Footbridge case, Push requires a substantially higher utilitarian credence (~70%) because the deontological penalty for using a person as a means is severe. This captures the empirical pattern: most people approve of diverting but not pushing, consistent with moderate utilitarian credences. Okabe-Ito palette."
#| dev: [png, pdf]
#| fig-width: 10
#| fig-height: 5
#| dpi: 300
# Prepare long-format data for both variants
classic_long <- emv_classic |>
select(w_u, EMV_Divert, EMV_NotDivert) |>
pivot_longer(-w_u, names_to = "action", values_to = "EMV") |>
mutate(action = gsub("EMV_", "", action),
variant = "Classic Trolley")
foot_long <- emv_footbridge |>
select(w_u, EMV_Push, EMV_NotPush) |>
pivot_longer(-w_u, names_to = "action", values_to = "EMV") |>
mutate(action = gsub("EMV_", "", action),
variant = "Footbridge")
combined <- bind_rows(classic_long, foot_long)
combined$variant <- factor(combined$variant, levels = c("Classic Trolley", "Footbridge"))
p_emv <- ggplot(combined, aes(x = w_u, y = EMV, color = action)) +
geom_line(linewidth = 1) +
facet_wrap(~variant) +
geom_hline(yintercept = 0, linetype = "dotted", color = "grey60") +
scale_color_manual(values = c("Divert" = okabe_ito[3], "NotDivert" = okabe_ito[6],
"Push" = okabe_ito[3], "NotPush" = okabe_ito[6]),
labels = c("Divert/Push", "Not Divert/Not Push"),
name = "Action") +
labs(
title = "Trolley Problem under moral uncertainty",
subtitle = "Expected moral value vs utilitarian credence (remainder split equally: deontology + contractualism)",
x = "Utilitarian credence (w_U)",
y = "Expected moral value"
) +
theme_publication() +
theme(strip.text = element_text(face = "bold"))
p_emv
```
## Interactive figure
```{r}
#| label: fig-trolley-interactive
combined_text <- combined |>
mutate(text = paste0("w_U = ", round(w_u, 2),
"\nAction: ", action,
"\nEMV = ", round(EMV, 2),
"\nVariant: ", variant))
p_int <- ggplot(combined_text, aes(x = w_u, y = EMV, color = action, text = text)) +
geom_line(linewidth = 0.8) +
facet_wrap(~variant) +
scale_color_manual(values = c("Divert" = okabe_ito[3], "NotDivert" = okabe_ito[6],
"Push" = okabe_ito[3], "NotPush" = okabe_ito[6])) +
labs(x = "Utilitarian credence", y = "Expected moral value", color = "Action") +
theme_publication()
ggplotly(p_int, tooltip = "text") |>
config(displaylogo = FALSE,
modeBarButtonsToRemove = c("select2d", "lasso2d"))
```
## Credence space analysis
We can visualize the full credence simplex ($w_U + w_K + w_C = 1$) to find the regions where each action is optimal.
```{r}
#| label: fig-credence-simplex
# Generate points on the credence simplex
n_grid <- 50
simplex_points <- expand.grid(
w_u = seq(0, 1, length.out = n_grid),
w_k = seq(0, 1, length.out = n_grid)
) |>
filter(w_u + w_k <= 1) |>
mutate(w_c = 1 - w_u - w_k)
# Compute EMV for Footbridge at each point
simplex_emv <- simplex_points |>
rowwise() |>
mutate(
emv_push = w_u * 4 + w_k * (-8) + w_c * (-3),
emv_not = w_u * (-4) + w_k * 0 + w_c * (-2),
optimal = ifelse(emv_push > emv_not, "Push", "Not Push"),
emv_diff = emv_push - emv_not
) |> ungroup()
# Barycentric to Cartesian
simplex_emv <- simplex_emv |>
mutate(
cx = w_k + 0.5 * w_c,
cy = (sqrt(3)/2) * w_c
)
p_simp <- ggplot(simplex_emv, aes(x = cx, y = cy, fill = emv_diff,
text = paste0("w_U=", round(w_u,2),
" w_K=", round(w_k,2),
" w_C=", round(w_c,2),
"\nEMV diff: ", round(emv_diff,2)))) +
geom_tile(width = 0.02, height = 0.02) +
scale_fill_gradient2(low = okabe_ito[6], mid = "white", high = okabe_ito[3],
midpoint = 0, name = "EMV(Push) − EMV(Not)") +
annotate("text", x = 0, y = -0.04, label = "Utilitarian", size = 3) +
annotate("text", x = 1, y = -0.04, label = "Deontological", size = 3) +
annotate("text", x = 0.5, y = sqrt(3)/2 + 0.04, label = "Contractualist", size = 3) +
coord_fixed() +
labs(title = "Footbridge — optimal action across credence simplex",
subtitle = "Green = Push optimal; Orange = Not Push optimal") +
theme_publication() +
theme(axis.text = element_blank(), axis.title = element_blank(),
axis.ticks = element_blank(), axis.line = element_blank(),
panel.grid = element_blank())
ggplotly(p_simp, tooltip = "text") |>
config(displaylogo = FALSE,
modeBarButtonsToRemove = c("select2d", "lasso2d"))
```
## Interpretation
The formal analysis reveals why the Trolley Problem is philosophically significant: the two variants produce different optimal actions for a wide range of reasonable credences, even though the consequentialist calculus is identical. In the Classic Trolley case, Divert is supported by utilitarianism (save four net lives), contractualism (five people can reasonably reject inaction), and only weakly opposed by deontology (diverting involves a mild active killing). The result is that Divert is optimal for utilitarian credences above roughly 10% — nearly everyone should divert. The Footbridge case is starkly different: pushing requires utilitarian credence above roughly 70% because deontology assigns a severe penalty to using a person instrumentally as a means to save others (the Kantian prohibition). This pattern — most people approve of diverting but disapprove of pushing — is exactly what @thomson_1985 documented empirically and is now replicated in our Bayesian framework. The credence simplex visualization shows that the "Push" region occupies a relatively small corner of moral credence space dominated by strong utilitarianism. The game-theoretic approach to ethics does not tell us which moral theory is correct, but it does provide a rigorous framework for making decisions under moral uncertainty — and it explains why our intuitions shift across cases in a way that is consistent with holding a mixture of moral views simultaneously. This analysis connects moral philosophy to decision theory, Bayesian inference, and the broader game-theoretic toolkit explored throughout this site.
## Extensions & related tutorials
- [Mixed-strategy Nash equilibrium in 2×2 games](../../foundations/nash-equilibrium-mixed/) — the decision-theoretic foundations underlying moral uncertainty.
- [Voting paradoxes as strategic games](../voting-paradoxes-strategic/) — when ethical aggregation meets strategic incentives.
- [Mechanism design for fairness](../../mechanism-design/fairness-constraints/) — designing systems that satisfy ethical constraints.
- [AI alignment as a principal-agent problem](../../ai-ml-foundations-and-applications/ai-alignment-principal-agent/) — game-theoretic approaches to AI safety.
- [Evolutionary ethics — cooperation without morality](../../evolutionary-gt/evolutionary-ethics/) — how prosocial behaviour evolves.
## References
::: {#refs}
:::