Markov decision processes
A Markov decision process is the rulebook of an RL problem — states, actions, chances and rewards — built on the idea that where you are now is all you need to know.
- 15 min read
- 3 reading levels
- Updated
Read these first
On this page 9
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
The short answer
A Markov decision process is a written-down rulebook for a decision problem, built on one promise: where you are right now tells you everything you need.
Your history does not matter. Only your current position does.
The analogy you have already lived
Think of Snakes and Ladders. Your token is on square 47. To decide what happens next, does it matter that you got there by climbing a ladder, or by crawling up square by square? Not at all.
Hand the board to a stranger halfway through the game. They can play on perfectly. Everything that matters is visible on the board.
That property has a name. It is called the Markov property, after the Russian mathematician Andrey Markov, and it means: the present position contains all the useful information about the past.
Now add choices to Snakes and Ladders — say you could pick which of two dice to roll. That is a Markov decision process: a Markov world where somebody gets to choose.
Why we bother writing the rulebook down
The previous lesson gave you a loop: agent acts, environment responds. That is a picture, not something you can reason about.
A Markov decision process turns the picture into five listed pieces:
- States — every situation the world can be in. Every square on the board.
- Actions — what you may choose in each state.
- Chances — for each state and action, where you might end up, and how likely each landing spot is.
- Rewards — the score you get for being somewhere or doing something.
- Patience — how much you care about later rewards compared to now.
Once a problem is written in this form, every algorithm in this section applies to it. That is the whole payoff. You describe your delivery-routing problem in these five pieces, and Q-learning works on it without knowing anything about deliveries.
The idea that makes it solvable
Here is the trick that unlocked the entire field.
How good a square is depends on how good its neighbours are.
The square right next to the finish is worth a lot, because from there you are one step away. The square next to that is worth slightly less. The value seeps outward from the goal, one neighbour at a time, like water spreading on a floor.
. . . . . GOAL round 1: only the goal has value
. . . . x GOAL round 2: its neighbour picks some up
. . . x x GOAL round 3: the neighbour's neighbour
. . x x x GOAL ...keep going until nothing changesYou start with every square worth nothing. Then you sweep over the board again and again. Each time you ask one question: given what I believe about my neighbours, how good is this square? After enough sweeps the beliefs stop changing. When they stop, they are correct.
That procedure is called value iteration. It is a couple of dozen lines of code, and you will run it in the developer section.
Careful: this needs the rulebook
Value iteration requires you to already know the chances and the rewards — the full rulebook. For a board game you do. For a real robot in a real warehouse, you do not.
That is the dividing line of the whole subject.
- If you have the rulebook, you can plan. Sit down, compute the best move for every square, then go and play.
- If you do not have the rulebook, you must learn. Try things, watch what happens, and slowly build up an opinion.
Every lesson after this one is about the second case. This lesson exists because the second case is defined by what it is missing.
Where the Markov property quietly breaks
The promise "where you are now is enough" is often false, and knowing when is a real skill.
A single photo of a moving ball does not tell you which way it is going. Position alone is not enough; you need velocity too. The fix is to make the state richer — use four photos instead of one.
A card game where you have seen which cards have already been played. Your hand alone is not the state. What has been discarded is part of it.
A patient's current temperature does not say whether they are getting better or worse.
In every case the repair is the same: put the missing information into the state. That usually makes the state bigger, and a bigger state is harder to learn. That trade is one of the real design decisions in this field.
Where you have already seen an MDP
- Google Maps rerouting. State: where you are and current traffic. Actions: which road to take next. Reward: minus the minutes.
- An inventory system deciding how much stock to order. State: what is on the shelf. Actions: order sizes. Reward: sales, minus storage cost.
- A lift deciding which floor to serve next. State: who is waiting where.
- Cricket strategy. State: runs, wickets, overs left. Action: attack or defend.
Remember this
- A Markov decision process is states, actions, chances, rewards and patience, written down.
- The Markov property says the present state is enough; the path there does not matter.
- Value spreads outward from rewards, one neighbour per sweep, until it settles.
What to learn next
- Q-learning — solving an MDP when nobody hands you the rulebook.
- Exploration vs exploitation — the cost of not knowing the transition probabilities.
- Probability — the background the transition kernel assumes.
Developer — Code and libraries.
Setup
pip install numpyNothing else. This runs in a fraction of a second.
A room with a pillar and an open drain
The environment: a three by three room. You start bottom-left. The exit is top-right. There is a pillar you cannot walk through and an open drain you very much want to avoid.
The floor is slippery. You aim in one direction and there is a one-in-ten chance of sliding to each side instead. Walking into a wall leaves you exactly where you were.
Every step costs a tiny amount, so dawdling is discouraged.
import numpy as np
# A 3x3 room. S = start, # = pillar you cannot walk through,
# G = the exit (+1), H = an open drain (-1).
GRID = ["..G",
".#H",
"S.."]
ROWS, COLS = 3, 3
ACTIONS = {0: (-1, 0), 1: (1, 0), 2: (0, -1), 3: (0, 1)} # up, down, left, right
ARROW = {0: "^", 1: "v", 2: "<", 3: ">"}
GAMMA = 0.9 # how much a reward one step later is worth
SLIP = 0.1 # chance of sliding to each side instead
states = [(r, c) for r in range(ROWS) for c in range(COLS) if GRID[r][c] != "#"]
terminal = {(r, c) for r, c in states if GRID[r][c] in "GH"}
def move(state, action):
"""Where you land, honouring walls. Bumping a wall leaves you where you were."""
r, c = state
dr, dc = ACTIONS[action]
nr, nc = r + dr, c + dc
if 0 <= nr < ROWS and 0 <= nc < COLS and GRID[nr][nc] != "#":
return (nr, nc)
return state
def outcomes(state, action):
"""P(s' | s, a) as a list of (next state, probability). Sideways slips included."""
sideways = [2, 3] if action in (0, 1) else [0, 1]
result = {}
for prob, a in [(1 - 2 * SLIP, action), (SLIP, sideways[0]), (SLIP, sideways[1])]:
nxt = move(state, a)
result[nxt] = result.get(nxt, 0.0) + prob # two slips can land on the same square
return list(result.items())
def reward(state):
return {"G": 1.0, "H": -1.0}.get(GRID[state[0]][state[1]], -0.04) # small cost per step
V = {s: 0.0 for s in states}
for sweep in range(60):
newV = {}
for s in states:
if s in terminal:
newV[s] = reward(s)
continue
newV[s] = max(reward(s) + GAMMA * sum(p * V[n] for n, p in outcomes(s, a))
for a in ACTIONS)
delta = max(abs(newV[s] - V[s]) for s in states)
V = newV
if delta < 1e-9:
break
print(f"value iteration settled after {sweep + 1} sweeps\n")
print("how good is each square:")
for r in range(ROWS):
print(" " + " ".join(" # " if GRID[r][c] == "#" else f"{V[(r, c)]:+.2f}" for c in range(COLS)))
print("\nbest move from each square:")
for r in range(ROWS):
row = []
for c in range(COLS):
if GRID[r][c] == "#":
row.append("#")
elif (r, c) in terminal:
row.append(GRID[r][c])
else:
row.append(ARROW[max(ACTIONS, key=lambda a: sum(p * V[n] for n, p in outcomes((r, c), a)))])
print(" " + " ".join(row))value iteration settled after 31 sweeps how good is each square: +0.67 +0.83 +1.00 +0.54 # -1.00 +0.41 +0.31 +0.10 best move from each square: > > G ^ # H ^ < <
Read the policy, it is smarter than it looks
Start at the bottom-left. The plan is up, up, right, right — four moves, hugging the left wall, nowhere near the drain.
Now look at the bottom-right corner, the square directly below the drain. The best move there is left. Away from the exit.
Think about why. Moving up from there aims straight at the drain. Even aiming somewhere else, the slip could take you in. The safest thing to do is to back off and take the long way round. Nobody programmed that caution. It fell out of the numbers.
Look at the middle-bottom square. Its best move is also left, not up — because up is blocked by the pillar, so aiming up wastes a step and gains nothing.
The values tell the same story: +0.67, +0.83, +1.00 climbing along the top row, and +0.41, +0.31, +0.10 sagging along the bottom as you approach the drain.
Line by line, the parts that are not obvious
result[nxt] = result.get(nxt, 0.0) + prob — this addition is load-bearing. In a corner, aiming up and slipping left can both leave you on the same square. Overwrite instead of adding, and your probabilities no longer sum to one. Every hand-written MDP has this bug at least once.
max(... for a in ACTIONS) — this single max is the Bellman optimality equation. "The value of a square is the best you can do from it, counting the reward now plus a discounted average of where you might land."
reward(s) + GAMMA * sum(...) — reward now, plus patience times the expected value of the future. Set GAMMA = 0 and the agent becomes completely short-sighted: every square scores -0.04 and the arrows become meaningless.
delta < 1e-9 — the stopping test. Value iteration is guaranteed to converge, and the size of the largest change tells you how close you are. It ran 31 sweeps out of a permitted 60.
-0.04 per step — try -0.001 instead. The agent stops caring about time and may take a longer, even safer path. Try -0.5 and it becomes so desperate to finish that walking into the drain starts to look like a reasonable exit. Three characters, completely different behaviour.
Common mistakes
Updating V in place instead of building newV. Editing the dictionary you are reading gives you Gauss-Seidel value iteration. It still converges, and often faster, but your intermediate numbers will not match any textbook and you will lose an evening comparing.
Giving terminal states a future. A terminal state must not bootstrap from anything. Here they are pinned to their own reward. Leave that out and value cycles endlessly through the goal.
Assuming a policy is optimal because the values stopped changing to two decimal places. Values converge geometrically, but the greedy policy can still flip late. Check the policy, not only the numbers.
Trying this on a real problem. Value iteration touches every state on every sweep. A chess board has more states than atoms in the observable universe. This method is for small, fully known problems — and for building the intuition that the learning methods rest on.
Try it yourself
Move the drain from H at row 1 to the square below it, so the grid becomes ["..G", ".#.", "S.H"]. Predict which arrows change before you run it. Then set SLIP = 0.0 and watch the caution disappear entirely — with no slipping, walking right past the drain is perfectly safe.
What to learn next
- Q-learning — solving an MDP when nobody hands you the rulebook.
- Exploration vs exploitation — the cost of not knowing the transition probabilities.
- Probability — the background the transition kernel assumes.
Researcher — Mathematics and papers.
Definition
A finite MDP is a tuple $\mathcal{M} = (\mathcal{S}, \mathcal{A}, P, R, \gamma)$ where $\mathcal{S}$ and $\mathcal{A}$ are finite sets, $P : \mathcal{S} \times \mathcal{A} \to \Delta(\mathcal{S})$, $R : \mathcal{S} \times \mathcal{A} \to \mathbb{R}$ is bounded, and $\gamma \in [0,1)$.
The Markov property is the statement $$ \Pr(s_{t+1} = s' \mid s_t, a_t, s_{t-1}, a_{t-1}, \dots, s_0) = \Pr(s_{t+1} = s' \mid s_t, a_t) = P(s' \mid s_t, a_t) $$ so the conditional distribution of the next state is independent of everything before $s_t$ given $(s_t, a_t)$.
Value functions
For a policy $\pi$: $$ V^\pi(s) = \mathbb{E}\pi!\left[ \sum{k=0}^{\infty} \gamma^k r_{t+k+1} \;\middle|\; s_t = s \right], \qquad Q^\pi(s,a) = \mathbb{E}_\pi!\left[ G_t \mid s_t = s, a_t = a \right] $$
$V^\pi$ is the state-value function and $Q^\pi$ the action-value function. They satisfy the Bellman expectation equations:
$$ V^\pi(s) = \sum_a \pi(a \mid s) \left[ r(s,a) + \gamma \sum_{s'} P(s' \mid s,a) V^\pi(s') \right] $$
and the Bellman optimality equations:
$$ V^(s) = \max_a \left[ r(s,a) + \gamma \sum_{s'} P(s' \mid s,a) V^(s') \right], \qquad Q^(s,a) = r(s,a) + \gamma \sum_{s'} P(s' \mid s,a) \max_{a'} Q^(s',a') $$
Symbols: $r(s,a)$ is the expected immediate reward, $P(s' \mid s,a)$ the transition probability, $\gamma$ the discount factor, $V^$ and $Q^$ the optimal value functions. The optimal policy is $\pi^(s) = \arg\max_a Q^(s,a)$, and it is deterministic and stationary — a result that takes real work to prove, and one that holds for discounted infinite-horizon MDPs and fails for constrained or partially observed ones.
Why value iteration converges
Define the Bellman optimality operator $\mathcal{T} : \mathbb{R}^{|\mathcal{S}|} \to \mathbb{R}^{|\mathcal{S}|}$ by $$ (\mathcal{T}V)(s) = \max_a \left[ r(s,a) + \gamma \sum_{s'} P(s' \mid s,a) V(s') \right] $$
$\mathcal{T}$ is a $\gamma$-contraction in the supremum norm: $$ |\mathcal{T}V - \mathcal{T}U|\infty \le \gamma |V - U|\infty $$
The proof is short. For any $s$, $|(\mathcal{T}V)(s) - (\mathcal{T}U)(s)| \le \max_a \gamma \sum_{s'} P(s'|s,a) |V(s') - U(s')| \le \gamma |V-U|_\infty$, using $|\max_a f - \max_a g| \le \max_a |f-g|$ and that $P(\cdot|s,a)$ sums to one.
Banach's fixed-point theorem then gives a unique fixed point $V^$ and geometric convergence: $|V_k - V^|_\infty \le \gamma^k |V_0 - V^|\infty$. The practical stopping rule follows from it — if $|V{k+1} - V_k|\infty < \epsilon$ then $|V{k+1} - V^|_\infty < \epsilon\gamma/(1-\gamma)$.
Reaching $\epsilon$ accuracy needs $O!\left( \frac{\log(1/\epsilon(1-\gamma))}{1-\gamma} \right)$ iterations, each costing $O(|\mathcal{S}|^2|\mathcal{A}|)$. The $1/(1-\gamma)$ factor is why $\gamma = 0.999$ problems are expensive to plan in.
The three exact methods
| Method | Per-iteration cost | Iterations | Notes |
|---|---|---|---|
| Value iteration | $O(|\mathcal{S}|^2|\mathcal{A}|)$ | $O!\left(\frac{\log(1/\epsilon)}{1-\gamma}\right)$ | Simplest; converges geometrically |
| Policy iteration | $O(|\mathcal{S}|^3 + |\mathcal{S}|^2|\mathcal{A}|)$ | Often very few | Terminates exactly; strongly polynomial for fixed $\gamma$ (Ye, 2011) |
| Linear programming | polynomial | — | $\min_V \sum_s \mu(s) V(s)$ s.t. $V(s) \ge r(s,a) + \gamma \sum_{s'} P V(s')$ |
Policy iteration alternates evaluation (solve $V^\pi = r^\pi + \gamma P^\pi V^\pi$, a linear system) with improvement ($\pi'(s) = \arg\max_a Q^\pi(s,a)$). The policy improvement theorem guarantees $V^{\pi'} \ge V^\pi$ pointwise, and since there are finitely many deterministic policies, it terminates at an exact optimum. Ye (2011) proved it is strongly polynomial for fixed $\gamma$, resolving a long-open question.
The curse of dimensionality
$|\mathcal{S}|$ grows exponentially in the number of state variables. A robot arm with 7 joints discretised at 100 positions each has $10^{14}$ states. Exact dynamic programming is therefore restricted to small problems, and everything else in this section — Q-learning, DQN, policy gradients — exists to sidestep it, by sampling instead of sweeping and by approximating $V$ or $\pi$ with a function rather than a table.
Extensions worth knowing
- Average-reward MDPs replace discounting with $\rho^\pi = \lim_{T\to\infty} \frac{1}{T}\mathbb{E}[\sum_{t=1}^T r_t]$, and the Bellman equation acquires a bias term. Appropriate for genuinely continuing tasks where discounting is an artefact.
- Constrained MDPs (Altman, 1999) add expected-cost constraints. Optimal policies are then generally stochastic, and the LP formulation handles them naturally while value iteration does not.
- Semi-MDPs allow actions of variable duration, which is the formal basis of the options framework for hierarchical RL (Sutton, Precup and Singh, 1999).
- Robust MDPs (Iyengar, 2005; Nilim and El Ghaoui, 2005) optimise against a set of transition kernels, which matters whenever $P$ is estimated from data.
References
- Bellman (1957), Dynamic Programming — the origin, and the source of "curse of dimensionality".
- Howard (1960), Dynamic Programming and Markov Processes — policy iteration.
- Puterman (1994), Markov Decision Processes: Discrete Stochastic Dynamic Programming — the definitive reference.
- Ye (2011), The Simplex and Policy-Iteration Methods are Strongly Polynomial for the Markov Decision Problem with a Fixed Discount Rate.
- Sutton and Barto (2018), chapters 3 and 4.
What to learn next
- Q-learning — solving an MDP when nobody hands you the rulebook.
- Exploration vs exploitation — the cost of not knowing the transition probabilities.
- Probability — the background the transition kernel assumes.