Signaling games and perfect Bayesian equilibrium

foundations
signaling-games
perfect-bayesian-equilibrium
incomplete-information
Model strategic communication under asymmetric information in R, compute perfect Bayesian equilibria with separating and pooling strategies, and visualize how informed players signal their types.
Author

Raban Heller

Published

May 8, 2026

Modified

May 8, 2026

Keywords

signaling game, perfect Bayesian equilibrium, PBE, separating equilibrium, pooling equilibrium, Spence

Introduction & motivation

In many strategic situations, one party knows something the other does not — and can take costly actions to credibly reveal (or conceal) that information. Michael Spence’s 1973 job-market signaling model showed that high-ability workers may invest in education not because it raises productivity, but because it credibly signals their type to employers who cannot directly observe ability. This insight — that costly actions can serve as credible communication devices — pervades economics: firms signal quality through warranties, governments signal resolve through military mobilisation, and startups signal viability through burning cash. The formal analysis of such situations uses signaling games, a class of dynamic games of incomplete information where an informed Sender moves first (choosing a signal), and an uninformed Receiver updates beliefs and responds. The equilibrium concept is perfect Bayesian equilibrium (PBE): strategies must be sequentially rational given beliefs, and beliefs must be consistent with strategies via Bayes’ rule wherever possible. PBE refines the Bayes-Nash equilibrium concept for sequential games by requiring that players’ beliefs at every information set are explicitly specified and updated. The key distinction is between separating equilibria (different types send different signals, fully revealing their private information) and pooling equilibria (all types send the same signal, leaving the Receiver unable to distinguish them). This tutorial implements Spence’s signaling model in R, computes both equilibrium types, analyses the conditions under which each survives equilibrium refinements, and visualizes how the signaling cost structure determines which equilibrium prevails.

Mathematical formulation

A signaling game has: a Sender with type \(\theta \in \{\theta_L, \theta_H\}\) drawn from prior \(p(\theta_H) = \pi\); a signal space \(M\); and a Receiver who observes the signal \(m\) and chooses action \(a \in A\).

A perfect Bayesian equilibrium is: (i) a Sender strategy \(\sigma^*: \Theta \to \Delta(M)\); (ii) a Receiver strategy \(\alpha^*: M \to \Delta(A)\); (iii) a belief system \(\mu: M \to \Delta(\Theta)\) such that:

  1. Sequential rationality (Sender): \(\sigma^*(\theta) \in \arg\max_m u_S(\theta, m, \alpha^*(m))\)
  2. Sequential rationality (Receiver): \(\alpha^*(m) \in \arg\max_a \sum_\theta \mu(\theta|m) u_R(\theta, m, a)\)
  3. Belief consistency: \(\mu(\theta|m) = \frac{p(\theta)\sigma^*(m|\theta)}{\sum_{\theta'} p(\theta')\sigma^*(m|\theta')}\) for signals on the equilibrium path.

Spence’s education model: Worker type \(\theta \in \{\theta_L, \theta_H\}\) with productivity \(\theta_H > \theta_L > 0\). Education signal \(e \geq 0\) costs \(c(e, \theta) = e/\theta\) (cheaper for high types). Employer pays wage \(w = E[\theta|e]\).

  • Separating PBE: \(e^*(\theta_L) = 0\), \(e^*(\theta_H) = e^* \in [\theta_L(\theta_H - \theta_L), \theta_H(\theta_H - \theta_L)]\). Employer pays \(w(0) = \theta_L\), \(w(e^*) = \theta_H\).
  • Pooling PBE: Both types choose \(e = 0\). Employer pays \(w = \pi\theta_H + (1-\pi)\theta_L\).

R implementation

# --- Spence signaling model ---
theta_L <- 1   # low-type productivity
theta_H <- 2   # high-type productivity
pi_H <- 0.5    # prior on high type

# Signaling cost: c(e, theta) = e / theta
signal_cost <- function(e, theta) e / theta

# --- Separating equilibrium ---
# High type must signal enough that low type won't mimic
# IC for low type: theta_L - 0 >= theta_H - e*/theta_L  =>  e* >= theta_L*(theta_H - theta_L)
# IC for high type: theta_H - e*/theta_H >= theta_L - 0  =>  e* <= theta_H*(theta_H - theta_L)

e_min_sep <- theta_L * (theta_H - theta_L)
e_max_sep <- theta_H * (theta_H - theta_L)

cat("=== Spence Signaling Model ===\n")
=== Spence Signaling Model ===
cat(sprintf("Types: θ_L = %.0f, θ_H = %.0f, P(θ_H) = %.1f\n", theta_L, theta_H, pi_H))
Types: θ_L = 1, θ_H = 2, P(θ_H) = 0.5
cat(sprintf("\n--- Separating Equilibrium ---\n"))

--- Separating Equilibrium ---
cat(sprintf("Education range for high type: e* ∈ [%.1f, %.1f]\n", e_min_sep, e_max_sep))
Education range for high type: e* ∈ [1.0, 2.0]
cat(sprintf("Least-cost separating: e* = %.1f\n", e_min_sep))
Least-cost separating: e* = 1.0
# Payoffs under separating (least-cost)
e_star <- e_min_sep
payoff_L_sep <- theta_L - signal_cost(0, theta_L)     # low type: wage θ_L, no education
payoff_H_sep <- theta_H - signal_cost(e_star, theta_H)  # high type: wage θ_H, education e*

cat(sprintf("Low type: wage = %.1f, cost = 0, payoff = %.2f\n", theta_L, payoff_L_sep))
Low type: wage = 1.0, cost = 0, payoff = 1.00
cat(sprintf("High type: wage = %.1f, cost = %.2f, payoff = %.2f\n",
            theta_H, signal_cost(e_star, theta_H), payoff_H_sep))
High type: wage = 2.0, cost = 0.50, payoff = 1.50
# --- Pooling equilibrium ---
w_pool <- pi_H * theta_H + (1 - pi_H) * theta_L

cat(sprintf("\n--- Pooling Equilibrium (e = 0) ---\n"))

--- Pooling Equilibrium (e = 0) ---
cat(sprintf("Pooling wage: w = %.2f\n", w_pool))
Pooling wage: w = 1.50
payoff_L_pool <- w_pool
payoff_H_pool <- w_pool
cat(sprintf("Low type payoff: %.2f (gains %.2f vs separating)\n",
            payoff_L_pool, payoff_L_pool - payoff_L_sep))
Low type payoff: 1.50 (gains 0.50 vs separating)
cat(sprintf("High type payoff: %.2f (loses %.2f vs separating)\n",
            payoff_H_pool, payoff_H_sep - payoff_H_pool))
High type payoff: 1.50 (loses 0.00 vs separating)
# --- Welfare comparison ---
welfare_sep <- 0.5 * payoff_L_sep + 0.5 * payoff_H_sep
welfare_pool <- 0.5 * payoff_L_pool + 0.5 * payoff_H_pool
cat(sprintf("\nExpected welfare: separating = %.3f, pooling = %.3f\n", welfare_sep, welfare_pool))

Expected welfare: separating = 1.250, pooling = 1.500
cat("(Separating equilibrium is inefficient: education is pure signaling waste)\n")
(Separating equilibrium is inefficient: education is pure signaling waste)

Static publication-ready figure

# Build comparison data
eq_data <- tibble(
  type = rep(c("Low type (θ_L)", "High type (θ_H)"), 2),
  equilibrium = rep(c("Separating", "Pooling"), each = 2),
  wage = c(theta_L, theta_H, w_pool, w_pool),
  cost = c(0, signal_cost(e_star, theta_H), 0, 0),
  payoff = c(payoff_L_sep, payoff_H_sep, payoff_L_pool, payoff_H_pool)
) |> mutate(
  equilibrium = factor(equilibrium, levels = c("Separating", "Pooling")),
  component = "Payoff"
)

# Stacked: wage minus cost = payoff
bar_data <- eq_data |>
  select(type, equilibrium, payoff) |>
  mutate(label = sprintf("%.2f", payoff))

ggplot(bar_data, aes(x = type, y = payoff, fill = equilibrium)) +
  geom_col(position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = label), position = position_dodge(width = 0.7),
            vjust = -0.3, size = 3.5) +
  scale_fill_manual(values = c("Separating" = okabe_ito[5], "Pooling" = okabe_ito[1]),
                     name = "Equilibrium") +
  labs(title = "Signaling game payoffs: separating vs pooling equilibrium",
       subtitle = sprintf("θ_L=%d, θ_H=%d, P(θ_H)=%.1f; least-cost separating signal e*=%.1f",
                           theta_L, theta_H, pi_H, e_star),
       x = NULL, y = "Net payoff (wage − signaling cost)") +
  coord_cartesian(ylim = c(0, max(bar_data$payoff) * 1.15)) +
  theme_publication()
Figure 1: Figure 1. Spence’s education signaling model: separating vs pooling equilibrium payoffs for each worker type. In the separating equilibrium, the high-type worker invests in education to credibly signal ability, earning a higher wage but bearing signaling costs. In the pooling equilibrium, both types earn the average wage — benefiting the low type at the high type’s expense. The separating equilibrium is socially inefficient because education is pure waste (no productivity effect). Okabe-Ito palette.

Interactive figure

# How does the separating equilibrium range change with type spread?
theta_H_seq <- seq(1.1, 4, by = 0.05)
sep_data <- lapply(theta_H_seq, function(tH) {
  e_min <- theta_L * (tH - theta_L)
  e_max <- tH * (tH - theta_L)
  e_lc <- e_min  # least-cost separating
  payoff_H <- tH - e_lc / tH
  payoff_pool <- 0.5 * tH + 0.5 * theta_L
  tibble(theta_H = tH, e_min = e_min, e_max = e_max,
         payoff_sep_H = payoff_H, payoff_pool_H = payoff_pool)
}) |> bind_rows() |>
  pivot_longer(c(payoff_sep_H, payoff_pool_H),
               names_to = "equilibrium", values_to = "payoff") |>
  mutate(
    eq_label = ifelse(equilibrium == "payoff_sep_H", "Separating", "Pooling"),
    text = paste0("θ_H = ", round(theta_H, 2),
                  "\nEquilibrium: ", eq_label,
                  "\nHigh-type payoff: ", round(payoff, 3))
  )

p_sens <- ggplot(sep_data, aes(x = theta_H, y = payoff, color = eq_label, text = text)) +
  geom_line(linewidth = 1) +
  scale_color_manual(values = c("Separating" = okabe_ito[5], "Pooling" = okabe_ito[1]),
                      name = "Equilibrium") +
  labs(title = "High-type payoff: separating vs pooling as type spread varies",
       subtitle = "As θ_H increases, separating becomes more attractive (signaling cost grows slower than wage gain)",
       x = "High-type productivity (θ_H)", y = "High-type net payoff") +
  theme_publication()

ggplotly(p_sens, tooltip = "text") |>
  config(displaylogo = FALSE, modeBarButtonsToRemove = c("select2d", "lasso2d"))
Figure 2

Interpretation

Signaling games formalize one of the deepest insights in information economics: when direct communication is cheap (costless talk), it is often not credible, but costly actions can serve as credible signals precisely because they are differentially costly across types. In Spence’s model, education works as a signal not because it makes workers more productive, but because high-ability workers find it less costly to acquire — creating a natural screening device. The separating equilibrium is informationally efficient (employers learn the worker’s true type) but socially wasteful (education resources are burned purely for signaling). The pooling equilibrium avoids this waste but creates adverse selection: high types subsidise low types through the averaged wage, potentially driving high types out of the market. The tension between these equilibria is resolved by equilibrium refinements — the Cho-Kreps intuitive criterion eliminates many pooling equilibria by requiring “reasonable” off-path beliefs, typically selecting the least-cost separating equilibrium as the unique surviving outcome. The sensitivity analysis reveals that signaling becomes more attractive to the high type as the productivity gap widens: the wage premium from separation grows linearly in \(\theta_H\), while the signaling cost grows sub-linearly (because \(e^*/\theta_H = \theta_L(1 - \theta_L/\theta_H)\) is bounded). Perfect Bayesian equilibrium is the workhorse solution concept for these sequential incomplete-information games — it extends subgame perfection by requiring explicit beliefs at every information set and Bayesian updating on the equilibrium path, while leaving off-path beliefs as a degree of freedom that refinements then discipline.

References

Back to top

Reuse

Citation

BibTeX citation:
@online{heller2026,
  author = {Heller, Raban},
  title = {Signaling Games and Perfect {Bayesian} Equilibrium},
  date = {2026-05-08},
  url = {https://r-heller.github.io/equilibria/tutorials/foundations/signaling-games-pbe/},
  langid = {en}
}
For attribution, please cite this work as:
Heller, Raban. 2026. “Signaling Games and Perfect Bayesian Equilibrium.” May 8. https://r-heller.github.io/equilibria/tutorials/foundations/signaling-games-pbe/.