Markov Decision Processes
MDPs give you the mathematical language to turn "act wisely under uncertainty over time" into a precise optimization problem — and solve it exactly.
Overview
Every interesting decision in the world unfolds over time. You don't just pick one action and collect one reward — you act, observe consequences, act again, and accumulate rewards across a trajectory that might span seconds, years, or the entire lifetime of a system. How should a chess engine evaluate a position? How should a robot plan to navigate a maze? How should a recommendation system balance exploitation (show what you know the user likes) against exploration (discover new preferences)? All of these are sequential decision problems, and Markov Decision Processes (MDPs) are the mathematical foundation for reasoning about them rigorously.
An MDP is a formal model of an agent interacting with a stochastic environment. The agent observes a state, takes an action, receives a reward, and transitions to a new state — over and over. The goal is to find a policy: a mapping from states to actions that maximizes the total expected reward accumulated over time. What makes MDPs beautiful is that they reduce this potentially daunting optimization over infinite sequences of decisions to a clean recursive equation — the Bellman equation — that can be solved by simple, provably correct algorithms.
MDPs are not just an abstraction. They are the backbone of nearly every serious planning and control algorithm in AI. Dynamic programming (Bellman, 1957), optimal control, model-based reinforcement learning, game-playing AI, robot motion planning, scheduling, inventory control — all speak the language of MDPs. Understanding MDPs deeply is understanding the theoretical core of modern AI. When DeepMind's AlphaGo learned to beat human Go champions, and when OpenAI's agents learned to play Dota 2 from scratch, the fundamental framework underneath was an MDP, solved (approximately) by reinforcement learning.
This module builds the full picture: from the formal definition of an MDP, through the Bellman equations for value functions, to the value iteration and policy iteration algorithms, and finally to the conceptual bridge toward reinforcement learning — where the agent no longer knows the model and must learn from experience. By the end, you will not only understand the theory but be able to implement and analyze these algorithms from scratch.
MDPs are the foundational abstraction for any AI system that must make sequences of decisions under uncertainty. Without MDPs, there is no principled way to talk about "long-term consequences" or "planning ahead." Every reinforcement learning algorithm — from Q-learning to PPO to AlphaZero — is solving an MDP, either with a known model or by learning one from interaction. The Bellman equation, which is the heart of MDP theory, also underlies dynamic programming broadly, connecting MDPs to computer science fundamentals like shortest paths, sequence alignment, and optimal control.
In real systems, the impact is enormous. Robotics uses MDPs for motion planning under actuator noise and sensor uncertainty. Operations research uses them for inventory, queueing, and scheduling. Healthcare uses MDPs to model treatment decisions over time. Financial engineering uses them for portfolio optimization and option pricing. Modern LLM fine-tuning (RLHF — Reinforcement Learning from Human Feedback) is, at its core, solving an MDP where the states are conversation prefixes and the rewards come from human preference signals. Understanding MDPs at a deep level — not just the surface formulas — gives you a durable lens through which every sequential decision problem becomes analyzable.
Bellman Optimality Backup in an MDP
The Bellman optimality backup at state s: branch over actions, then over stochastic transitions, compute expected Q-values, and take the max. This is one application of the Bellman operator T and the fundamental operation of value iteration.
The concepts
01Sequential Decision Making and the Need for a Formal Model
The Setup
Imagine you are navigating a gridworld — a 4×4 grid of cells. Some cells have rewards (a treasure chest, a pit of fire). At each step you can move up, down, left, or right. The floor is slippery: when you try to move right, 80% of the time you succeed, but 10% of the time you slide up and 10% you slide down. At the end of some path through this grid, you reach a terminal state and collect your cumulative reward.
This is not a simple optimization — you cannot just pick the action with the highest immediate reward, because an action that looks bad now might lead to a great state later. And the environment is stochastic, so you can't plan a deterministic path; you need to decide what to do in each possible state you might land in.
The key insight: a good decision rule must be a function of the current state, not just a fixed sequence of actions. Such a function is called a policy.
Why Single-Step Isn't Enough
In a single-step ("bandit") problem, you pull an arm, get a reward, done. But in sequential problems, today's action affects tomorrow's state. This temporal dependency is the distinguishing feature. To handle it, we need a model that tracks:
- What states exist
- What actions are available
- How actions cause state transitions
- What rewards are received
- How to aggregate rewards over time
MDPs provide exactly this model.
# Gridworld as intuition pump
# States: (row, col) in a 4x4 grid
# Actions: UP, DOWN, LEFT, RIGHT
# Transition: intended direction with prob 0.8, perpendicular with 0.1 each
# Reward: +1 at goal (3,3), -1 at pit (3,1), 0 elsewhere
# Terminal: goal and pit are absorbing states
GRID_SIZE = 4
GOAL = (3, 3)
PIT = (3, 1)
REWARDS = {GOAL: +1.0, PIT: -1.0}
def is_terminal(s):
return s in (GOAL, PIT)
02The Formal MDP Definition
Five Components
A Markov Decision Process is a 5-tuple $(\mathcal{S}, \mathcal{A}, T, R, \gamma)$:
| Symbol | Name | Meaning |
|---|---|---|
| $\mathcal{S}$ | State space | All possible situations the agent can be in |
| $\mathcal{A}$ | Action space | All possible actions (can depend on state) |
| $T(s, a, s')$ | Transition function | $P(s' \mid s, a)$ — probability of landing in $s'$ after action $a$ in state $s$ |
| $R(s, a, s')$ | Reward function | Immediate reward received for that transition |
| $\gamma \in [0, 1)$ | Discount factor | How much we value future rewards relative to immediate ones |
Some formulations write $R(s, a)$ or $R(s)$ — these are simplifications of the general $R(s, a, s')$.
The Markov Property
The Markov property is the key modeling assumption: the future depends on the past only through the current state. Formally: $$P(s_{t+1} \mid s_t, a_t, s_{t-1}, a_{t-1}, \ldots, s_0, a_0) = P(s_{t+1} \mid s_t, a_t) = T(s_t, a_t, s_{t+1})$$
This says: if you know where you are now, knowing your entire history gives you zero additional predictive power. The state must be a sufficient statistic for the future. This is why defining state correctly is the art of MDP modeling — a poorly defined state that omits relevant history violates the Markov property and will give wrong answers.
The Discount Factor
The discount factor $\gamma$ captures the time value of reward. A reward received now is worth more than the same reward received later (for $\gamma < 1$). Mathematically, it ensures the infinite sum of rewards converges: $$G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}$$
If $\gamma = 0$: agent is myopic, only cares about immediate reward.
If $\gamma \to 1$: agent cares equally about all future rewards (requires finite episodes for convergence).
Typically $\gamma = 0.9$ or $0.99$.
Analogy: Think of $\gamma$ as an inflation rate. A dollar today is worth more than a dollar in a year. Setting $\gamma = 0.9$ means a reward 10 steps away is worth $0.9^{10} \approx 0.35$ of its face value today.
from dataclasses import dataclass, field
from typing import Dict, Tuple, List
State = int # or any hashable
Action = int
@dataclass
class MDP:
states: List[State]
actions: List[Action]
# T[s][a] -> list of (prob, next_state, reward)
T: Dict[State, Dict[Action, List[Tuple[float, State, float]]]]
gamma: float = 0.9
def transitions(self, s: State, a: Action):
"""Return list of (probability, next_state, reward)."""
return self.T[s][a]
03Policies and the Objective
What Is a Policy?
A policy $\pi$ is a rule for selecting actions. The most general form is a stochastic policy: $\pi(a \mid s) = P(A_t = a \mid S_t = s)$. A deterministic policy is a special case: $\pi(s) = a$.
For fully observable MDPs (which is what we study here), it is sufficient to consider stationary deterministic policies: the action depends only on the current state, not on time or history. This is a deep theorem — the optimal policy for a discounted infinite-horizon MDP is always stationary and deterministic. You don't need to remember the past or act differently at different time steps.
The Return and the Objective
Given a policy $\pi$, starting from state $s_0$, the agent follows a trajectory $s_0, a_0, s_1, a_1, \ldots$ The return is the discounted cumulative reward: $$G_0 = \sum_{t=0}^{\infty} \gamma^t R_{t+1}$$ The return is a random variable (because transitions are stochastic). The objective is to find a policy that maximizes the expected return from every state: $$\pi^* = \arg\max_\pi ; \mathbb{E}\pi\left[\sum{t=0}^{\infty} \gamma^t R_{t+1} \mid S_0 = s\right] \quad \forall s \in \mathcal{S}$$
Key Insight: A Single Policy Can Simultaneously Maximize From Every State
A remarkable (and non-obvious) fact: there exists a single policy $\pi^*$ that is simultaneously optimal starting from every state. You don't need different policies for different starting states. This is unique to MDPs and is what makes them tractable.
import random
def run_episode(mdp: MDP, policy: dict, start: State, max_steps=1000) -> float:
"""
Simulate one episode under a deterministic policy.
Returns discounted total reward.
"""
s = start
total_reward = 0.0
discount = 1.0
for _ in range(max_steps):
a = policy[s]
outcomes = mdp.transitions(s, a) # [(prob, s', r), ...]
probs = [o[0] for o in outcomes]
idx = random.choices(range(len(outcomes)), weights=probs)[0]
_, s_next, r = outcomes[idx]
total_reward += discount * r
discount *= mdp.gamma
s = s_next
return total_reward
04Value Functions: V* and Q*
The State-Value Function
The state-value function (or just value function) $V^\pi(s)$ under policy $\pi$ is the expected return starting from state $s$ and following $\pi$ thereafter: $$V^\pi(s) = \mathbb{E}\pi\left[\sum{t=0}^{\infty} \gamma^t R_{t+1} \mid S_0 = s\right]$$
The optimal value function $V^(s)$ is the maximum over all policies: $$V^(s) = \max_\pi V^\pi(s)$$
If you know $V^*$, extracting the optimal policy is easy — at each state, pick the action that leads to the highest expected value.
The Action-Value Function (Q-function)
The action-value function (Q-function) $Q^\pi(s, a)$ is the expected return starting from state $s$, taking action $a$ (which may or may not be what $\pi$ would do), then following $\pi$: $$Q^\pi(s, a) = \mathbb{E}\pi\left[\sum{t=0}^{\infty} \gamma^t R_{t+1} \mid S_0 = s, A_0 = a\right]$$
The optimal Q-function: $$Q^(s, a) = \mathbb{E}\left[R_{t+1} + \gamma \max_{a'} Q^(S_{t+1}, a') \mid S_t = s, A_t = a\right]$$
Why Q* Is Critical for Model-Free RL
$V^$ is beautiful for analysis, but to extract the optimal policy from $V^$ you need to know the transition model $T$ (to compute the expected next-state value). $Q^$ encodes the action quality directly — to find the optimal action at state $s$, just compute $\arg\max_a Q^(s, a)$, no model needed. This is why Q-learning (a model-free RL algorithm) targets $Q^*$.
Relationship
$$V^(s) = \max_a Q^(s, a)$$ $$Q^(s, a) = \sum_{s'} T(s, a, s') \left[R(s, a, s') + \gamma V^(s')\right]$$
# V* to optimal policy extraction (requires model T)
def extract_policy(mdp: MDP, V: dict) -> dict:
policy = {}
for s in mdp.states:
best_a, best_q = None, float('-inf')
for a in mdp.actions:
q = sum(p * (r + mdp.gamma * V[s_next])
for p, s_next, r in mdp.transitions(s, a))
if q > best_q:
best_q, best_a = q, a
policy[s] = best_a
return policy
# Q* to optimal policy (model-free — no T needed at decision time)
def extract_policy_from_Q(Q: dict) -> dict:
# Q[(s,a)] -> value
states = set(s for s, a in Q)
actions = set(a for s, a in Q)
return {s: max(actions, key=lambda a: Q[(s, a)]) for s in states}
05The Bellman Equations
Recursive Structure of Value
The key mathematical insight behind MDPs is that the value function has a recursive structure. The value of being in state $s$ under policy $\pi$ equals the immediate reward you expect plus the discounted value of the next state:
$$V^\pi(s) = \sum_a \pi(a|s) \sum_{s'} T(s,a,s') \left[R(s,a,s') + \gamma V^\pi(s')\right]$$
This is the Bellman expectation equation. It says: the value of a state is the expected reward from taking your policy's action, plus the discounted expected value of where you land.
Bellman Optimality Equation
For the optimal value function, the Bellman optimality equation replaces the policy average with a max:
$$\boxed{V^(s) = \max_a \sum_{s'} T(s,a,s') \left[R(s,a,s') + \gamma V^(s')\right]}$$
This is one of the most important equations in all of AI. Let's unpack it:
- For each action $a$ you could take from $s$...
- Sum over all possible next states $s'$ (weighted by transition probability $T(s,a,s')$)...
- The value of landing in $s'$ is $R(s,a,s')$ (immediate) plus $\gamma V^*(s')$ (discounted future optimal value).
- Take the max over actions — because at $s$, the optimal agent acts optimally.
Why Is This Useful?
The Bellman optimality equation is a system of equations — one per state. There are $|\mathcal{S}|$ unknowns ($V^*(s)$ for each state) and $|\mathcal{S}|$ equations. If the equations were linear, you could solve them directly. They're not linear (because of the max), but they define a contraction mapping — a key fact that guarantees iterative methods converge.
The Contraction Mapping Theorem
Define the Bellman optimality operator $\mathcal{T}$: $$(\mathcal{T}V)(s) = \max_a \sum_{s'} T(s,a,s')\left[R(s,a,s') + \gamma V(s')\right]$$
$V^$ is the unique fixed point of $\mathcal{T}$: $\mathcal{T}V^ = V^*$. Moreover, $\mathcal{T}$ is a $\gamma$-contraction in the $\ell^\infty$ norm: $$|\mathcal{T}V - \mathcal{T}U|\infty \leq \gamma |V - U|\infty$$
By the Banach fixed-point theorem, repeated application of $\mathcal{T}$ to any initial $V_0$ converges to $V^*$ at a geometric rate $\gamma^k$. This is the theoretical foundation of value iteration.
# Bellman operator applied to a value function V
# Returns the "one-step improved" value function
def bellman_operator(mdp: MDP, V: dict) -> dict:
V_new = {}
for s in mdp.states:
# max over actions of Q(s, a)
V_new[s] = max(
sum(p * (r + mdp.gamma * V[s_next])
for p, s_next, r in mdp.transitions(s, a))
for a in mdp.actions
)
return V_new
# The contraction: ||TV - TU||_inf <= gamma * ||V - U||_inf
# guarantees geometric convergence to V*
06Value Iteration
Algorithm
Value Iteration (Bellman, 1957) directly exploits the contraction property. It initializes $V$ arbitrarily and repeatedly applies the Bellman operator until convergence:
Initialize V(s) = 0 for all s
Repeat:
For each state s:
V(s) ← max_a Σ_{s'} T(s,a,s') [R(s,a,s') + γ V(s')]
Until max_s |V_new(s) - V_old(s)| < ε
Extract policy: π(s) = argmax_a Σ_{s'} T(s,a,s') [R(s,a,s') + γ V(s')]
Convergence Guarantee
After $k$ iterations: $$|V_k - V^*|\infty \leq \frac{\gamma^k}{1-\gamma} |V_1 - V_0|\infty$$
To achieve $|V_k - V^*|_\infty < \epsilon$, you need $k \geq \frac{\log(1/\epsilon(1-\gamma))}{\log(1/\gamma)}$ iterations.
Complexity
Each iteration scans all states and actions:
- Time per iteration: $O(|\mathcal{S}|^2 |\mathcal{A}|)$ in the worst case (each transition can reach any state)
- Memory: $O(|\mathcal{S}||\mathcal{A}|)$ for the transition table
- Number of iterations: Polynomial in $\frac{1}{1-\gamma}$ and $\log(1/\epsilon)$
This is polynomial time in the size of the MDP description. Value iteration is one of the few algorithms in AI with such clean guarantees.
Asynchronous Value Iteration
You don't need to update all states simultaneously in each sweep. Asynchronous value iteration updates states one at a time (in any order). As long as every state is updated infinitely often, the algorithm still converges to $V^*$. This is important for large state spaces where a full sweep is expensive — you can prioritize states that are likely to change the most (prioritized sweeping).
def value_iteration(mdp: MDP, epsilon: float = 1e-6) -> dict:
"""
Solve MDP exactly via value iteration.
Returns V* as a dict {state: value}.
"""
V = {s: 0.0 for s in mdp.states}
while True:
delta = 0.0
V_new = {}
for s in mdp.states:
# Bellman optimality backup
v = max(
sum(p * (r + mdp.gamma * V[s_next])
for p, s_next, r in mdp.transitions(s, a))
for a in mdp.actions
)
V_new[s] = v
delta = max(delta, abs(v - V[s]))
V = V_new
if delta < epsilon:
break
# Extract optimal policy
policy = extract_policy(mdp, V)
return V, policy
# Convergence criterion: max change across all states < epsilon
# Error bound: ||V_k - V*||_inf <= gamma/(1-gamma) * delta_k
07Policy Iteration
A Different Approach
Policy Iteration (Howard, 1960) interleaves two phases:
- Policy Evaluation: Given current policy $\pi$, compute $V^\pi$ exactly (or approximately).
- Policy Improvement: Given $V^\pi$, compute a better (greedy) policy $\pi'$.
Repeat until the policy doesn't change.
Policy Evaluation
For a fixed policy $\pi$, the Bellman expectation equations become a linear system: $$V^\pi(s) = \sum_{s'} T(s, \pi(s), s') \left[R(s, \pi(s), s') + \gamma V^\pi(s')\right]$$
This is $|\mathcal{S}|$ linear equations in $|\mathcal{S}|$ unknowns. You can solve it exactly with linear algebra ($O(|\mathcal{S}|^3)$ with Gaussian elimination), or iteratively by applying the Bellman expectation operator repeatedly (converges since $\gamma < 1$).
Policy Improvement Theorem
Given $V^\pi$, define the greedy policy: $$\pi'(s) = \arg\max_a \sum_{s'} T(s,a,s')\left[R(s,a,s') + \gamma V^\pi(s')\right]$$
Theorem: $V^{\pi'}(s) \geq V^\pi(s)$ for all $s$, with strict inequality somewhere unless $\pi = \pi^*$.
This is the Policy Improvement Theorem — switching to the greedy policy with respect to $V^\pi$ can only make things better or keep them the same. When the policy stops changing, it must be optimal.
Convergence: Finite Steps to Optimality
Since the number of deterministic policies is $|\mathcal{A}|^{|\mathcal{S}|}$ (finite), and each policy iteration step strictly improves the policy (or stops), policy iteration terminates in finite steps — typically far fewer than value iteration. In practice, policy iteration often converges in 5–20 iterations for moderate MDPs, while value iteration might need hundreds.
Modified Policy Iteration
A hybrid: instead of solving the linear system exactly in the evaluation step, do only $k$ steps of iterative evaluation. With $k=1$, you recover value iteration. With $k \to \infty$, you get full policy iteration. $k = 5$–$20$ often hits the sweet spot in practice.
import numpy as np
def policy_evaluation(mdp: MDP, policy: dict, epsilon=1e-6) -> dict:
"""Iterative policy evaluation for deterministic policy."""
V = {s: 0.0 for s in mdp.states}
while True:
delta = 0.0
for s in mdp.states:
v = sum(p * (r + mdp.gamma * V[s_next])
for p, s_next, r in mdp.transitions(s, policy[s]))
delta = max(delta, abs(v - V[s]))
V[s] = v
if delta < epsilon:
return V
def policy_iteration(mdp: MDP) -> dict:
"""Solve MDP via policy iteration. Returns optimal policy."""
# Start with any policy (e.g., always take first action)
policy = {s: mdp.actions[0] for s in mdp.states}
while True:
V = policy_evaluation(mdp, policy)
policy_new = extract_policy(mdp, V) # greedy improvement
if policy_new == policy:
return policy, V # converged to optimal policy
policy = policy_new
08Value Iteration vs Policy Iteration: Trade-offs
Side-by-Side Comparison
| Property | Value Iteration | Policy Iteration |
|---|---|---|
| Core operation | Bellman max backup | Policy eval (linear solve) + greedy improvement |
| Convergence type | Asymptotic ($\gamma$-geometric) | Finite (in $ |
| Typical iterations | 100s–1000s | 5–20 |
| Cost per iteration | $O( | \mathcal{S} |
| Memory | $O( | \mathcal{S} |
| Parallelism | Easy (each state independent) | Harder (linear solve) |
When to Use Which
- Value iteration is simpler to implement, naturally asynchronous, and easy to parallelize. It's preferred when states are numerous and you can afford approximate convergence.
- Policy iteration converges in fewer outer iterations but each iteration is more expensive. It's preferred for moderate-sized MDPs where you want the exact optimal policy without many sweeps.
- Linear programming is a third exact method: solve $V^* = \arg\min_V \sum_s V(s)$ subject to $V(s) \geq \sum_{s'} T(s,a,s')[R(s,a,s') + \gamma V(s')]$ for all $s, a$. It's $O(|\mathcal{S}|^{3.5})$ with interior-point methods — competitive for very small state spaces.
Practical Reality
For real-world problems, the state space is often enormous (the classic curse of dimensionality: a robot arm with 10 joints, each with 1000 positions, has $10^{30}$ states — table-based methods are hopeless). This is where approximate dynamic programming, function approximation, and reinforcement learning enter.
09The Curse of Dimensionality and the Bridge to RL
Why Exact Methods Break Down
Both value iteration and policy iteration require:
- An explicit representation of $T(s, a, s')$ for all $(s, a, s')$ triples — the model.
- A table for $V(s)$ or $Q(s, a)$ for all states — tabular representation.
Both assumptions fail in practice. For a robot, the state is a continuous vector of joint angles and velocities. For a game of Go, the state space is $3^{361} \approx 10^{172}$. For language modeling, the state is a sequence of tokens — essentially infinite.
The solution path splits into two directions:
- Model-based planning with function approximation: Approximate $T$ and $R$ with learned models, and approximate $V^$ or $Q^$ with neural networks.
- Model-free reinforcement learning: Don't learn $T$ at all. Instead, estimate $V^$ or $Q^$ directly from experience (sampled transitions).
What RL Assumes
In reinforcement learning, the agent does NOT know $T$ or $R$. It only sees:
- The current state $s_t$
- The reward $r_{t+1}$ received after taking action $a_t$
- The next state $s_{t+1}$
Despite this, the goal is still to solve the same MDP. The Bellman equations still define the objective. The algorithms are stochastic approximation versions of value/policy iteration:
| RL Algorithm | MDP Counterpart | Estimate |
|---|---|---|
| Q-learning | Value iteration on $Q^*$ | Stochastic Bellman backups |
| SARSA | Value iteration on $Q^\pi$ | On-policy Bellman backups |
| Policy Gradient | Policy iteration | Gradient ascent on $J(\pi_\theta)$ |
| Actor-Critic | Modified policy iteration | Value + policy networks |
The Q-learning Update (Bridge Equation)
The canonical RL update that mirrors the Bellman optimality equation: $$Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right]$$
The term in brackets is the Temporal Difference (TD) error — the difference between the current estimate $Q(s,a)$ and the one-step Bellman target $r + \gamma \max_{a'} Q(s', a')$. This is a stochastic approximation to applying $\mathcal{T}$ — the Bellman operator — using a single sampled transition instead of the full expectation over all next states.
# Q-learning: model-free RL that estimates Q* via TD updates
import random
def q_learning(env, states, actions, gamma=0.9, alpha=0.1,
epsilon=0.1, episodes=10000):
"""
env.step(s, a) -> (s_next, reward, done)
Returns learned Q table.
"""
Q = {(s, a): 0.0 for s in states for a in actions}
for _ in range(episodes):
s = env.reset()
done = False
while not done:
# epsilon-greedy exploration
if random.random() < epsilon:
a = random.choice(actions)
else:
a = max(actions, key=lambda a: Q[(s, a)])
s_next, r, done = env.step(s, a)
# Bellman target (uses max over next actions)
target = r + gamma * max(Q[(s_next, a_)] for a_ in actions)
# TD update — stochastic approximation of Bellman operator
Q[(s, a)] += alpha * (target - Q[(s, a)])
s = s_next
return Q
10Worked Example: Solving a Small MDP by Hand
The Problem
Consider a 3-state MDP: states ${A, B, C}$ where $C$ is terminal (absorbing, $V^*(C) = 0$). Actions: ${left, right}$. Transitions are deterministic. Rewards are received on leaving a state. $\gamma = 0.9$.
| State | Action | Next State | Reward |
|---|---|---|---|
| A | right | B | 0 |
| A | left | A | -1 |
| B | right | C | +10 |
| B | left | A | 0 |
We want $V^(A)$, $V^(B)$, $V^*(C)$.
Value Iteration
Initialize: $V_0 = {A: 0, B: 0, C: 0}$
Iteration 1:
- $V_1(C) = 0$ (terminal)
- $V_1(B) = \max[right: 10 + 0.9 \cdot 0, ; left: 0 + 0.9 \cdot 0] = \max[10, 0] = 10$
- $V_1(A) = \max[right: 0 + 0.9 \cdot 0, ; left: -1 + 0.9 \cdot 0] = \max[0, -1] = 0$
Iteration 2:
- $V_2(C) = 0$
- $V_2(B) = \max[right: 10 + 0.9 \cdot 0, ; left: 0 + 0.9 \cdot 0] = 10$ (no change)
- $V_2(A) = \max[right: 0 + 0.9 \cdot 10, ; left: -1 + 0.9 \cdot 0] = \max[9, -1] = 9$
Iteration 3:
- $V_3(A) = \max[right: 0 + 0.9 \cdot 10, ; left: -1 + 0.9 \cdot 9] = \max[9, 7.1] = 9$ (no change)
Converged! $V^* = {A: 9, B: 10, C: 0}$.
Optimal Policy
$\pi^(A) = right$, $\pi^(B) = right$. The agent goes $A \to B \to C$, collecting reward $0 + 10 = 10$, discounted: $0 + 0.9 \cdot 10 = 9$ starting from A. Confirmed.
Intuition Check
Why does $V^*(A) = 9$ and not $10$? Because to get the $+10$ reward at $B$, the agent must first travel from $A$ to $B$ (one step, zero reward), and the future is discounted: $\gamma^1 \times 10 = 0.9 \times 10 = 9$.
# Verify by hand calculation
mdp_states = ['A', 'B', 'C']
mdp_actions = ['left', 'right']
# T[s][a] = [(prob, s', r)]
T = {
'A': {'right': [(1.0, 'B', 0.0)],
'left': [(1.0, 'A', -1.0)]},
'B': {'right': [(1.0, 'C', 10.0)],
'left': [(1.0, 'A', 0.0)]},
'C': {'right': [(1.0, 'C', 0.0)], # absorbing
'left': [(1.0, 'C', 0.0)]},
}
V = {'A': 0.0, 'B': 0.0, 'C': 0.0}
gamma = 0.9
for iteration in range(10):
V_new = {}
for s in mdp_states:
V_new[s] = max(
sum(p * (r + gamma * V[s2])
for p, s2, r in T[s][a])
for a in mdp_actions
)
V = V_new
print(f"Iter {iteration+1}: {V}")
# Output:
# Iter 1: {'A': 0.0, 'B': 10.0, 'C': 0.0}
# Iter 2: {'A': 9.0, 'B': 10.0, 'C': 0.0}
# Iter 3: {'A': 9.0, 'B': 10.0, 'C': 0.0} <- converged
11Partially Observable MDPs (POMDPs): A Glimpse Beyond
The Fully Observable Assumption
Everything above assumed the agent observes the true state $s_t$ directly. This is the fully observable MDP. But real environments are often only partially observable: a robot doesn't know its exact position (sensor noise), a card game player doesn't see opponents' hands, a medical AI doesn't know the patient's true underlying health state.
The POMDP Extension
A Partially Observable MDP (POMDP) adds:
- An observation space $\mathcal{O}$
- An observation function $O(s', a, o) = P(O_{t+1} = o \mid S_{t+1} = s', A_t = a)$
The agent receives observation $o_t$ instead of the true state $s_t$. The optimal strategy must maintain a belief state $b_t$ — a probability distribution over states: $b_t(s) = P(S_t = s \mid o_1, a_1, o_2, a_2, \ldots, o_t)$.
Critically, the belief state IS a sufficient statistic for the future: $b_t$ is Markov over the history. So POMDPs can be re-framed as MDPs over the belief space — but the belief space is a continuous simplex of dimension $|\mathcal{S}| - 1$, making exact solution exponentially hard in general (PSPACE-complete).
Why This Matters
POMDPs are the principled model for real-world AI agents. Modern deep RL handles partial observability through recurrent neural networks (LSTM/GRU state as implicit belief) or explicit memory mechanisms. Understanding that the POMDP belief update is Bayesian inference — and that the Bellman equation still applies to beliefs — connects MDP theory all the way to modern transformers-as-agents research.
The takeaway: full MDPs are the essential building block, but real problems demand extending the framework — and the structure (Bellman equations, value functions, policy optimization) carries over intact.
# Belief state update (Bayesian filter) for a POMDP
def belief_update(belief: dict, a, o,
T: dict, # T[s][a] = [(p, s', r)]
O: dict # O[s'][a][o] = P(o | s', a)
) -> dict:
"""
Update belief b over states given action a and observation o.
belief: {state: probability}
Returns new normalized belief.
"""
new_belief = {}
for s_next in belief:
# P(s' | b, a) = Σ_s T(s,a,s') * b(s)
pred = sum(p * belief[s]
for s in belief
for p, sp, _ in T[s][a]
if sp == s_next)
# P(o | s', a) * P(s' | b, a)
new_belief[s_next] = O[s_next][a][o] * pred
# Normalize
total = sum(new_belief.values())
return {s: v / total for s, v in new_belief.items()}
Key insights
- ✦The Markov property is a modeling choice, not a physical law — designing a state representation that satisfies it is the central skill in applying MDPs to real problems.
- ✦The Bellman optimality equation is not just a recursive formula — it is a fixed-point equation, and its contractiveness (with factor γ) is what guarantees that iterative algorithms converge to the unique optimal solution.
- ✦There always exists a deterministic stationary optimal policy for a discounted infinite-horizon MDP — you never need to randomize or remember history to act optimally.
- ✦Value iteration and policy iteration are both applications of the Bellman operator: value iteration applies the operator once per iteration, policy iteration applies it to convergence (policy evaluation) before each greedy improvement step.
- ✦The discount factor γ does double duty: it models the time value of reward AND ensures the infinite-horizon return is finite and the Bellman operator is a contraction.
- ✦Q* is strictly more powerful than V* for model-free decision-making: argmax over Q*(s,·) gives the optimal action without needing the transition model T.
- ✦Q-learning is simply stochastic value iteration on Q*: each experience tuple (s, a, r, s') provides one noisy sample of the Bellman optimality operator, and α-weighted updates implement stochastic approximation.
- ✦The curse of dimensionality makes tabular methods infeasible for real-world state spaces — but the Bellman equations remain the target; only the representation (tables → neural networks) changes.
Common pitfalls
- ⚠Forgetting to define state correctly: if your state doesn't capture all information relevant to the future, the Markov property is violated and Bellman-based algorithms give wrong answers. A classic mistake is using only current position in a problem where velocity matters.
- ⚠Using γ = 1 with an infinite-horizon MDP: the return becomes an infinite sum that may diverge, and the Bellman operator is no longer a contraction. Always use γ < 1, or ensure episodes terminate.
- ⚠Confusing V*(s) with Q*(s,a): V* requires the model T to extract actions; Q* does not. Model-free RL algorithms must target Q*, not V*.
- ⚠Off-by-one errors in the Bellman backup: the reward R(s, a, s') is the immediate reward for transitioning to s', while γV*(s') is the discounted future value at s'. Mixing up which state the reward belongs to is a frequent implementation bug.
- ⚠Premature convergence in value iteration: stopping when one sweep shows small change, without accounting for the error bound ||V_k - V*|| ≤ γ/(1-γ) * δ_k. The policy can still be suboptimal even when δ_k is small if γ is close to 1.
- ⚠Assuming the optimal policy is unique: there can be multiple optimal policies (ties in the max). This doesn't affect the uniqueness of V* but can matter for reproducibility and safety.
- ⚠Ignoring stochasticity when debugging: testing a policy in a deterministic simulation (e.g., always taking the most likely transition) and concluding it's optimal, when the stochastic environment reveals different behavior.
- ⚠Conflating reward and value: reward R(s,a,s') is immediate and local; value V*(s) is the long-run optimal expected cumulative reward. A state with zero immediate reward can have high value if it leads to rewarding states.
Practice
- 01medium
Implement value iteration for the 4x4 gridworld described in the first concept (slippery floor: 0.8 forward, 0.1 perpendicular each). Use γ = 0.9. Visualize V* and π* for the grid. Then change γ to 0.5 and observe how the optimal policy changes near the pit. Explain the difference.
- 02hard
Prove the Policy Improvement Theorem from scratch: if π' is greedy with respect to V^π, then V^π'(s) ≥ V^π(s) for all s. Your proof should use the Bellman expectation equation for V^π and the definition of the greedy policy.
- 03medium
Implement both value iteration and policy iteration for the same randomly generated MDP (5 states, 3 actions, random sparse T, γ=0.95). Compare: (a) number of iterations to convergence, (b) wall-clock time, (c) final V* agreement. Plot convergence curves.
- 04easy
Show by construction that the discount factor γ controls the 'effective horizon' of the agent. Specifically: define the effective horizon as the smallest H such that rewards beyond step H contribute less than ε = 0.01 to the total return. Derive a formula for H(γ, ε) and plot H as a function of γ for γ ∈ [0.5, 0.999].
- 05hard
Implement Q-learning for the 4x4 gridworld from Exercise 1 (same setup, but now you are NOT given T — the agent must learn from interaction). Run for 50,000 episodes with ε-greedy exploration (ε=0.1, α=0.1). Compare the learned Q-values to the exact Q* from value iteration. What does the error look like as a function of episode count?
- 06medium
Design an MDP (not a gridworld) for a real problem of your choice — e.g., a stock trading agent, a scheduling system, or a medical treatment decision. Explicitly define S, A, T, R, γ and justify your state representation. Identify at least one place where the Markov property might be violated by a naive state definition, and explain how to fix it.
Resources
Sutton & Barto — the canonical RL/MDP textbook. Chapters 3-4 cover MDPs, Bellman equations, value iteration, and policy iteration with exceptional clarity. Free PDF from the authors.
The standard undergraduate AI textbook. Chapter 17 is the definitive MDP chapter for undergrads.
MIT OCW material covering planning and decision making under uncertainty.
Percy Liang's CS221 has excellent slide decks on MDPs, value/policy iteration, and RL. Freely available on the course site.
Rigorous graduate-level treatment. Bertsekas proved many of the convergence results used in this module.
Lilian Weng's blog post — arguably the best single-page survey connecting MDPs to all major RL algorithms. Extremely well-written with clear math.
DeepMind's David Silver teaches the canonical UCL RL course. Lecture 2 is the definitive video treatment of MDPs, Bellman equations, and value/policy iteration.
OpenAI's introduction to key concepts in deep RL. Excellent conceptual framing of MDPs and the bridge to function approximation.
Checkpoints — you can now…
- ✓You can state the 5-tuple definition of an MDP and explain precisely what the Markov property requires of the state representation.
- ✓You can write down the Bellman optimality equation for V* from memory, label every term, and explain why the max appears instead of an expectation over actions.
- ✓You can trace through two iterations of value iteration by hand on a small MDP (3-5 states) and show how V converges toward V*.
- ✓You can implement value iteration and policy iteration in Python for a tabular MDP and verify they produce the same V* and π*.
- ✓You can explain the Policy Improvement Theorem and why policy iteration terminates in finite steps while value iteration only converges asymptotically.
- ✓You can explain why Q*(s,a) is more useful than V*(s) for model-free settings, and write down the Q-learning TD update as a stochastic approximation of the Bellman operator.
- ✓You can explain what breaks when the state space is continuous or exponentially large, and describe at least one approach (function approximation, model-free RL) that addresses this.
- ✓You can describe the belief-state formulation of a POMDP and explain why maintaining a belief state (rather than the raw observation) restores the Markov property.