Bayesian games — strategic interaction under incomplete information

bayesian-methods
bayesian-games
incomplete-information
bayes-nash
Formalise games of incomplete information in R, compute Bayes-Nash equilibria for type-dependent strategies, and visualize how private information shapes equilibrium behaviour.
Author

Raban Heller

Published

May 8, 2026

Modified

May 8, 2026

Keywords

Bayesian game, incomplete information, Bayes-Nash equilibrium, types, beliefs, Harsanyi

Introduction & motivation

In many strategic situations, players hold private information — a firm does not know a competitor’s costs, a buyer does not know a seller’s reservation price, a country does not know an adversary’s resolve. John Harsanyi (1973) showed how to handle this by introducing types: each player’s private information is summarised as a “type” drawn from a commonly known probability distribution. A Bayesian game combines the strategic structure of a normal-form game with this type uncertainty, and the solution concept is the Bayes-Nash equilibrium (BNE): a strategy profile where each type of each player best-responds given their beliefs about others’ types. Harsanyi’s framework unified the treatment of incomplete information in game theory and earned him the 1994 Nobel Prize alongside Nash and Selten. This tutorial formalises Bayesian games, implements BNE computation for discrete-type and continuous-type games, and demonstrates how private information changes equilibrium behaviour relative to complete-information benchmarks — with applications to pricing under cost uncertainty and entry deterrence with unknown strength.

Mathematical formulation

A Bayesian game \(G = (N, \{A_i\}, \{\Theta_i\}, \{u_i\}, \{p_i\})\) consists of:

  • Players \(N = \{1, \ldots, n\}\)
  • Action sets \(A_i\) for each player
  • Type spaces \(\Theta_i\) with common prior \(p(\theta)\)
  • Utility functions \(u_i(a, \theta)\) depending on the action profile and type profile

A (pure) strategy is a function \(s_i: \Theta_i \to A_i\). A Bayes-Nash equilibrium is a strategy profile \(s^*\) such that for all \(i\), all \(\theta_i \in \Theta_i\):

\[ s_i^*(\theta_i) \in \arg\max_{a_i \in A_i} E_{\theta_{-i}}[u_i(a_i, s_{-i}^*(\theta_{-i}), \theta_i, \theta_{-i}) \mid \theta_i] \]

Each type maximises expected utility given their beliefs about others’ types and strategies.

R implementation

# --- Example: Cournot duopoly with cost uncertainty ---
# Firm 1 knows its cost c1; Firm 2's cost c2 is either Low or High
# Inverse demand: P(Q) = a - Q, Q = q1 + q2

a <- 10  # demand intercept

# Firm 1: c1 = 2 (known)
c1 <- 2

# Firm 2: c2_L = 1 (prob 0.5), c2_H = 4 (prob 0.5)
c2_L <- 1; c2_H <- 4; prob_L <- 0.5

# BNE: Each type of Firm 2 best-responds; Firm 1 best-responds to expected behaviour
# Firm 1 BR: q1 = (a - c1 - E[q2]) / 2
# Firm 2 type L BR: q2_L = (a - c2_L - q1) / 2
# Firm 2 type H BR: q2_H = (a - c2_H - q1) / 2
# E[q2] = prob_L * q2_L + (1-prob_L) * q2_H

# Solve system:
# q1 = (a - c1 - prob_L * q2_L - (1-prob_L) * q2_H) / 2
# q2_L = (a - c2_L - q1) / 2
# q2_H = (a - c2_H - q1) / 2

# Substituting:
# q2_L = (a - c2_L)/2 - q1/2
# q2_H = (a - c2_H)/2 - q1/2
# E[q2] = prob_L*(a-c2_L)/2 + (1-prob_L)*(a-c2_H)/2 - q1/2
# q1 = (a - c1)/2 - E[q2]/2
# q1 = (a-c1)/2 - [prob_L*(a-c2_L)/2 + (1-prob_L)*(a-c2_H)/2 - q1/2]/2

E_c2 <- prob_L * c2_L + (1 - prob_L) * c2_H
# Simplified: standard Cournot with Firm 1 facing E[c2] adjustment
# q1 = (a - c1 - (a - E_c2)/2 + q1/4) ... solving the 3-equation system

# Direct solve via linear system: Ax = b
# q1 + 0.5*prob_L*q2_L + 0.5*(1-prob_L)*q2_H = (a-c1)/2
# 0.5*q1 + q2_L = (a-c2_L)/2
# 0.5*q1 + q2_H = (a-c2_H)/2

A_mat <- matrix(c(1, prob_L/2, (1-prob_L)/2,
                   0.5, 1, 0,
                   0.5, 0, 1), nrow = 3, byrow = TRUE)
b_vec <- c((a - c1)/2, (a - c2_L)/2, (a - c2_H)/2)
sol <- solve(A_mat, b_vec)

q1_star <- sol[1]; q2_L_star <- sol[2]; q2_H_star <- sol[3]

cat("=== Cournot Duopoly with Cost Uncertainty ===\n")
=== Cournot Duopoly with Cost Uncertainty ===
cat(sprintf("Firm 1 (c1=%.0f): q1* = %.3f\n", c1, q1_star))
Firm 1 (c1=2): q1* = 2.833
cat(sprintf("Firm 2 Low cost (c2=%.0f): q2_L* = %.3f\n", c2_L, q2_L_star))
Firm 2 Low cost (c2=1): q2_L* = 3.083
cat(sprintf("Firm 2 High cost (c2=%.0f): q2_H* = %.3f\n", c2_H, q2_H_star))
Firm 2 High cost (c2=4): q2_H* = 1.583
cat(sprintf("\nComplete-info benchmark (c2=E[c2]=%.1f):\n", E_c2))

Complete-info benchmark (c2=E[c2]=2.5):
q1_ci <- (a - 2*c1 + E_c2) / 3
q2_ci <- (a - 2*E_c2 + c1) / 3
cat(sprintf("  q1 = %.3f, q2 = %.3f\n", q1_ci, q2_ci))
  q1 = 2.833, q2 = 2.333
cat("(Incomplete information changes quantities: low-cost type produces more, high-cost less)\n")
(Incomplete information changes quantities: low-cost type produces more, high-cost less)

Static publication-ready figure

output_df <- tibble(
  scenario = c("Complete info", "BNE: Firm 1", "BNE: Firm 2 (Low)", "BNE: Firm 2 (High)"),
  q = c(q1_ci, q1_star, q2_L_star, q2_H_star),
  player = c("Benchmark", "Firm 1", "Firm 2", "Firm 2"),
  type = c("Complete info", "Uncertain", "Low cost", "High cost")
) |> mutate(scenario = factor(scenario, levels = scenario))

ggplot(output_df, aes(x = scenario, y = q, fill = type)) +
  geom_col(width = 0.6) +
  geom_text(aes(label = round(q, 2)), vjust = -0.3, size = 3.5) +
  scale_fill_manual(values = c("Complete info" = okabe_ito[8], "Uncertain" = okabe_ito[5],
                                "Low cost" = okabe_ito[3], "High cost" = okabe_ito[6]),
                     name = "Type") +
  labs(title = "Cournot outputs: complete vs incomplete information",
       subtitle = "Bayesian game with cost uncertainty (P(low)=0.5)",
       x = NULL, y = "Quantity produced") +
  theme_publication()
Figure 1: Figure 1. Cournot equilibrium outputs under complete vs incomplete information. Under incomplete information, Firm 2’s low-cost type produces more and high-cost type produces less than the complete-information benchmark. Firm 1, facing type uncertainty, adjusts output to the expected competitor. The information gap creates efficiency losses compared to the full-information case. Okabe-Ito palette.

Interactive figure

prob_seq <- seq(0.01, 0.99, 0.02)
bne_data <- lapply(prob_seq, function(p) {
  A_m <- matrix(c(1, p/2, (1-p)/2, 0.5, 1, 0, 0.5, 0, 1), nrow = 3, byrow = TRUE)
  b_v <- c((a-c1)/2, (a-c2_L)/2, (a-c2_H)/2)
  s <- solve(A_m, b_v)
  tibble(prob_low = p, q1 = s[1], q2_L = s[2], q2_H = s[3])
}) |> bind_rows() |>
  pivot_longer(-prob_low, names_to = "player", values_to = "quantity") |>
  mutate(text = paste0("P(low cost) = ", round(prob_low, 2), "\n", player, " = ", round(quantity, 3)))

p_sens <- ggplot(bne_data, aes(x = prob_low, y = quantity, color = player, text = text)) +
  geom_line(linewidth = 1) +
  scale_color_manual(values = c(q1 = okabe_ito[5], q2_L = okabe_ito[3], q2_H = okabe_ito[6]),
                      labels = c("Firm 1", "Firm 2 (Low)", "Firm 2 (High)"), name = "Player/Type") +
  labs(title = "BNE quantities vs prior belief about cost type",
       subtitle = "As P(low cost) increases, Firm 1 expects tougher competition → produces less",
       x = "P(Firm 2 has low cost)", y = "Equilibrium quantity") +
  theme_publication()
ggplotly(p_sens, tooltip = "text") |>
  config(displaylogo = FALSE, modeBarButtonsToRemove = c("select2d", "lasso2d"))
Figure 2

Interpretation

Bayesian games reveal how private information fundamentally reshapes strategic interaction. In the Cournot example, Firm 2’s private cost information creates a separation of types: the low-cost type produces aggressively (leveraging its cost advantage) while the high-cost type retreats. Firm 1, unable to observe Firm 2’s type, must respond to an expected competitor — producing a compromise quantity. The sensitivity analysis shows that beliefs matter: as Firm 1 becomes more convinced that Firm 2 has low costs, it reduces its own output (expecting tougher competition), while the low-cost type expands and the high-cost type’s output barely changes. Harsanyi (1973)’s framework transformed this from an intractable problem (“how do you play a game when you don’t know your opponent’s payoffs?”) into a standard optimisation problem with beliefs. The key insight is that incomplete information does not make games unsolvable — it makes them richer, with strategies contingent on private types and equilibria reflecting the interplay of information and incentives.

References

Harsanyi, John C. 1973. “Games with Randomly Disturbed Payoffs: A New Rationale for Mixed-Strategy Equilibrium Points.” International Journal of Game Theory 2 (1): 1–23. https://doi.org/10.1007/BF01737554.
Back to top

Reuse

Citation

BibTeX citation:
@online{heller2026,
  author = {Heller, Raban},
  title = {Bayesian Games — Strategic Interaction Under Incomplete
    Information},
  date = {2026-05-08},
  url = {https://r-heller.github.io/equilibria/tutorials/bayesian-methods/bayesian-games-incomplete-information/},
  langid = {en}
}
For attribution, please cite this work as:
Heller, Raban. 2026. “Bayesian Games — Strategic Interaction Under Incomplete Information.” May 8. https://r-heller.github.io/equilibria/tutorials/bayesian-methods/bayesian-games-incomplete-information/.