Skip to content
AI360Xpert
Beta

Thompson Sampling

Instead of guessing or relying on crude random exploration, the agent maintains a probability curve over each option's true payout and plays whichever arm produces the highest random sample from its belief.

Thompson Sampling draws a random sample from each arm's posterior distribution and selects the arm with the highest sample, balancing exploration and exploitation through probability matching.
Thompson Sampling draws a random sample from each arm's posterior distribution and selects the arm with the highest sample, balancing exploration and exploitation through probability matching.

Why Does This Exist?

In multi-armed bandit problems and reinforcement learning, an agent repeatedly chooses actions with unknown payout distributions, such as allocating traffic in clinical trials, optimizing recommender systems, or managing digital ad bids.

Standard exploration heuristics suffer from fundamental limitations:

  • ε\varepsilon-greedy explores blindly. With probability ε\varepsilon, it picks among actions uniformly at random, spending valuable attempts on arms already proven to be poor. Furthermore, fixed ε\varepsilon parameters induce linear regret, while ad-hoc decay schedules fail to adapt to dynamically changing uncertainty.
  • Upper Confidence Bound (UCB) applies deterministic optimism in the face of uncertainty (Q(a)+cln⁡t/N(a)Q(a) + c \sqrt{\ln t / N(a)}). While asymptotically effective, UCB requires tuning the exploration constant cc and can over-explore suboptimal actions that have low sample counts even when their empirical likelihood of being optimal is negligible.

Thompson Sampling (also known as posterior sampling or probability matching) resolves this dilemma by formulating action selection within a Bayesian framework. Instead of maintaining single-point averages or artificial upper bounds, the agent maintains a full posterior probability distribution over the unknown parameters of each arm. In each round, it draws a sample from each posterior and greedily plays the arm that generated the highest draw.

Consequently, each action is selected with a probability exactly equal to the posterior probability that it is the optimal action:

P(At=a)=P(θa=max⁡a′θa′∣Ht−1)\mathbb{P}(A_t = a) = \mathbb{P}\left(\theta_a = \max_{a'} \theta_{a'} \mid \mathcal{H}_{t-1}\right)

Arms with high epistemic uncertainty produce wide posterior distributions whose optimistic tails occasionally yield winning samples, driving natural exploration. As an arm is pulled and its uncertainty narrows, its posterior collapses around its true mean, smoothly transitioning the agent from exploration to exploitation without hand-crafted schedules.

Think of It Like This

The Casino Gambler with Customized Probability Dice

Imagine walking into a casino with a row of unfamiliar slot machines. Instead of recording a single running average for each machine, you carry a pouch of custom, multi-sided dice. The numbers on each die represent what you currently believe that specific machine is capable of paying out.

  • For a machine you pulled 40 times that almost always pays zero, your die is loaded with low numbers (mostly 1s and 2s)—its performance is well-known and disappointing.
  • For a machine you pulled 40 times that consistently delivers jackpots, your die is loaded with 8s, 9s, and 10s—high performance with narrow variance.
  • For a brand-new slot machine you have never touched, your die is completely unweighted with equal faces from 1 to 10—representing maximal uncertainty.

Before inserting every coin, your rule is simple: roll the dice for all machines simultaneously, compare the numbers rolled, and pull whichever machine produced the highest roll.

Most rounds, the loaded jackpot die rolls a 9 or 10 and wins, leading you to exploit your best known machine. However, every so often, the unplayed machine rolls a lucky 10 on its high-variance die and beats the reliable machine, prompting you to explore it. When that happens, you pull the new machine and observe the payout. If it hits, you replace low faces on its die with high ones; if it comes up empty, you shave off its high numbers.

Where the analogy stops: Physical dice have discrete integer faces and static physics. In Thompson Sampling, the "dice" are continuous probability distributions (such as Beta distributions) updated in real time via Bayes' theorem, allowing uncertainty to shrink smoothly with every observation.

How It Actually Works

Posterior Sampling and Beta-Bernoulli Conjugacy

Let A={1,2,…,K}\mathcal{A} = \{1, 2, \dots, K\} denote a set of KK distinct actions (bandit arms). Each arm kk is characterized by an unknown true success probability θk∗∈[0,1]\theta_k^* \in [0, 1].

At each round t=1,2,…,Tt = 1, 2, \dots, T:

  1. The agent selects an arm at∈Aa_t \in \mathcal{A}.
  2. The environment generates a binary reward rt∈{0,1}r_t \in \{0, 1\} from a Bernoulli likelihood:

rt∼Bernoulli(θat∗),P(rt=1∣θat∗)=θat∗r_t \sim \text{Bernoulli}(\theta_{a_t}^*), \quad \mathbb{P}(r_t = 1 \mid \theta_{a_t}^*) = \theta_{a_t}^*

To maintain Bayesian beliefs over each arm's parameter θk∗\theta_k^*, the agent places an independent Beta prior on each arm:

θk∼Beta(αk,βk)\theta_k \sim \text{Beta}(\alpha_k, \beta_k)

where αk>0\alpha_k > 0 represents prior pseudo-counts of successes, and βk>0\beta_k > 0 represents prior pseudo-counts of failures. An uninformative flat prior corresponds to αk=1\alpha_k = 1 and βk=1\beta_k = 1, which is equivalent to Uniform(0,1)\text{Uniform}(0, 1).

The Beta probability density function is:

f(θ;α,β)=θα−1(1−θ)β−1B(α,β),θ∈[0,1]f(\theta; \alpha, \beta) = \frac{\theta^{\alpha - 1}(1 - \theta)^{\beta - 1}}{\mathrm{B}(\alpha, \beta)}, \quad \theta \in [0, 1]

where B(α,β)=Γ(α)Γ(β)Γ(α+β)\mathrm{B}(\alpha, \beta) = \frac{\Gamma(\alpha)\Gamma(\beta)}{\Gamma(\alpha + \beta)} is the Beta function. The expected value and variance are:

E[θ]=αα+β,Var(θ)=αβ(α+β)2(α+β+1)\mathbb{E}[\theta] = \frac{\alpha}{\alpha + \beta}, \quad \mathrm{Var}(\theta) = \frac{\alpha \beta}{(\alpha + \beta)^2(\alpha + \beta + 1)}

The Thompson Sampling Decision Cycle:

  1. Posterior Sampling: For each arm k∈{1,…,K}k \in \{1, \dots, K\}, draw an independent sample from its posterior distribution: θ^k∼Beta(αk,βk)\hat{\theta}_k \sim \text{Beta}(\alpha_k, \beta_k)
  2. Action Selection: Select the arm with the highest sampled value: at=arg⁡max⁡k∈{1,…,K}θ^ka_t = \arg\max_{k \in \{1, \dots, K\}} \hat{\theta}_k
  3. Environment Feedback: Pull arm ata_t and observe reward rt∈{0,1}r_t \in \{0, 1\}.
  4. Bayesian Conjugate Update: Because the Beta distribution is conjugate to the Bernoulli likelihood, the posterior remains Beta. Update the parameters of the chosen arm ata_t: αat←αat+rt\alpha_{a_t} \leftarrow \alpha_{a_t} + r_t βat←βat+(1−rt)\beta_{a_t} \leftarrow \beta_{a_t} + (1 - r_t) Unchosen arms (k≠atk \neq a_t) retain their previous parameter values.

Worked numerical example

Consider a 3-armed bandit problem (K=3K = 3). At round t=1t = 1, the agent holds the following beliefs:

  • Arm 1 (Poor history): α1=3,β1=9  ⟹  E[θ1]=312=0.250\alpha_1 = 3, \beta_1 = 9 \implies \mathbb{E}[\theta_1] = \frac{3}{12} = 0.250
  • Arm 2 (High confidence, strong performance): α2=18,β2=6  ⟹  E[θ2]=1824=0.750\alpha_2 = 18, \beta_2 = 6 \implies \mathbb{E}[\theta_2] = \frac{18}{24} = 0.750
  • Arm 3 (High uncertainty / underexplored): α3=2,β3=2  ⟹  E[θ3]=24=0.500\alpha_3 = 2, \beta_3 = 2 \implies \mathbb{E}[\theta_3] = \frac{2}{4} = 0.500

Round 1:

  • Step 1 (Sample): The agent draws random values from each arm's posterior:
    • θ^1∼Beta(3,9)→0.264\hat{\theta}_1 \sim \text{Beta}(3, 9) \rightarrow 0.264
    • θ^2∼Beta(18,6)→0.718\hat{\theta}_2 \sim \text{Beta}(18, 6) \rightarrow 0.718
    • θ^3∼Beta(2,2)→0.812\hat{\theta}_3 \sim \text{Beta}(2, 2) \rightarrow 0.812
  • Step 2 (Select): arg⁡max⁡{0.264,0.718,0.812}=Arm 3\arg\max \{0.264, 0.718, 0.812\} = \text{Arm 3}. Even though Arm 2 has a substantially higher mean (0.750>0.5000.750 > 0.500), Arm 3's high variance allows a draw from its optimistic upper tail, triggering exploration.
  • Step 3 (Observe): The agent plays Arm 3 and receives reward r1=1r_1 = 1.
  • Step 4 (Update):
    • α3←2+1=3\alpha_3 \leftarrow 2 + 1 = 3
    • β3←2+0=2\beta_3 \leftarrow 2 + 0 = 2
    • New mean: E[θ3]=33+2=0.600\mathbb{E}[\theta_3] = \frac{3}{3 + 2} = 0.600.

Round 2:

  • Step 1 (Sample): The agent draws new samples:
    • θ^1∼Beta(3,9)→0.215\hat{\theta}_1 \sim \text{Beta}(3, 9) \rightarrow 0.215
    • θ^2∼Beta(18,6)→0.763\hat{\theta}_2 \sim \text{Beta}(18, 6) \rightarrow 0.763
    • θ^3∼Beta(3,2)→0.628\hat{\theta}_3 \sim \text{Beta}(3, 2) \rightarrow 0.628
  • Step 2 (Select): arg⁡max⁡{0.215,0.763,0.628}=Arm 2\arg\max \{0.215, 0.763, 0.628\} = \text{Arm 2}. Arm 2 wins the draw, triggering exploitation of the proven leader.
  • Step 3 (Observe): The agent plays Arm 2 and receives reward r2=1r_2 = 1.
  • Step 4 (Update):
    • α2←18+1=19\alpha_2 \leftarrow 18 + 1 = 19
    • β2←6+0=6\beta_2 \leftarrow 6 + 0 = 6
    • New mean: E[θ2]=1919+6=0.760\mathbb{E}[\theta_2] = \frac{19}{19 + 6} = 0.760.

Round 3:

  • Step 1 (Sample): The agent draws new samples:
    • θ^1∼Beta(3,9)→0.312\hat{\theta}_1 \sim \text{Beta}(3, 9) \rightarrow 0.312
    • θ^2∼Beta(19,6)→0.735\hat{\theta}_2 \sim \text{Beta}(19, 6) \rightarrow 0.735
    • θ^3∼Beta(3,2)→0.540\hat{\theta}_3 \sim \text{Beta}(3, 2) \rightarrow 0.540
  • Step 2 (Select): arg⁡max⁡{0.312,0.735,0.540}=Arm 2\arg\max \{0.312, 0.735, 0.540\} = \text{Arm 2}. Arm 2 wins again.
  • Step 3 (Observe): The agent plays Arm 2 and receives reward r3=0r_3 = 0.
  • Step 4 (Update):
    • α2←19+0=19\alpha_2 \leftarrow 19 + 0 = 19
    • β2←6+1=7\beta_2 \leftarrow 6 + 1 = 7
    • New mean: E[θ2]=1919+7≈0.731\mathbb{E}[\theta_2] = \frac{19}{19 + 7} \approx 0.731. The failure slightly decreases the mean and narrows the variance around 0.7310.731, fine-tuning future sampling draws.

Code

import randomfrom typing import List

class BernoulliThompsonSampling:    """Multi-armed bandit solver using Bayesian Bernoulli Thompson Sampling."""
    def __init__(self, n_arms: int, alpha_prior: float = 1.0, beta_prior: float = 1.0) -> None:        self.n_arms = n_arms        # Alpha tracks successes (+1 per success), Beta tracks failures (+1 per failure)        self.alphas: List[float] = [alpha_prior] * n_arms        self.betas: List[float] = [beta_prior] * n_arms
    def select_arm(self) -> int:        """Draw one sample from each arm's posterior Beta distribution and pick the argmax."""        samples = [            random.betavariate(self.alphas[i], self.betas[i])            for i in range(self.n_arms)        ]        return samples.index(max(samples))
    def update(self, arm: int, reward: int) -> None:        """Update conjugate Beta posterior for the selected arm based on observed reward."""        self.alphas[arm] += reward        self.betas[arm] += (1 - reward)
    def expected_values(self) -> List[float]:        """Return the posterior mean payoff probability for each arm."""        return [            self.alphas[i] / (self.alphas[i] + self.betas[i])            for i in range(self.n_arms)        ]

# Simulation: 3 arms with unknown true success probabilitiesrandom.seed(42)true_rates = [0.20, 0.75, 0.40]bandit = BernoulliThompsonSampling(n_arms=len(true_rates))
pull_counts = [0] * len(true_rates)total_reward = 0n_rounds = 1000
for _ in range(n_rounds):    arm = bandit.select_arm()    reward = 1 if random.random() < true_rates[arm] else 0    bandit.update(arm, reward)    pull_counts[arm] += 1    total_reward += reward
print(f"Pulls per arm: {pull_counts}")print(f"Estimated means: {[round(m, 3) for m in bandit.expected_values()]}")print(f"Total reward: {total_reward} / {n_rounds}")
# -> expected output:# Pulls per arm: [10, 947, 43]# Estimated means: [0.333, 0.751, 0.578]# Total reward: 740 / 1000

Watch Out For

Assuming Gaussian Likelihoods for Conversion Rates and Stale Batch Updates

Practitioners commonly encounter two major failure modes when implementing Thompson Sampling in real-world systems:

  1. Gaussian Likelihood Mismatch: Modeling conversion rates (such as click-through rates bounded in [0,1][0, 1]) with Gaussian-Gaussian conjugate models rather than Beta-Bernoulli models. Gaussian posteriors possess infinite support and can generate samples outside [0,1][0, 1] (negative probabilities or values >1> 1). When true conversion rates are small (e.g. 0.5%0.5\%), symmetric Gaussian approximations distort the tails, leading to severely suboptimal arm allocations.
  2. Stale Batched Updates and Delayed Feedback: In production environments, conversion events often arrive hours or days after the initial exposure. When updates are performed in batches rather than sequentially, Thompson Sampling repeatedly draws from the same stale posterior, potentially sending massive traffic to an inferior variation before negative signals are registered.

The Fix: Always pair likelihood models with their natural conjugate families (Beta-Bernoulli for binary outcomes, Normal-Inverse-Gamma for unbounded continuous returns). Under delayed feedback, implement batched Thompson Sampling with virtual count penalties or conservative posterior approximations that account for pending in-flight exposures.

The Quick Version

  • Probability Matching: Thompson Sampling selects each arm in direct proportion to the posterior probability that it is the optimal arm.
  • Conjugate Mechanics: Under Bernoulli rewards, the Beta conjugate prior allows instant analytical updates (α←α+r\alpha \leftarrow \alpha + r, β←β+(1−r)\beta \leftarrow \beta + (1 - r)) without numerical integration or MCMC.
  • Organic Exploration: High epistemic uncertainty naturally stretches the posterior distribution, generating optimistic tail samples that trigger exploration without manual ε\varepsilon-decay or bound tuning.
  • Asymptotic Optimality: Achieves the theoretical lower bound on regret (O(log⁡T)O(\log T) per the Lai-Robbins bound) while consistently outperforming heuristic alternatives in empirical benchmarks.