Skip to content
AI360Xpert
Beta

Dynamic Programming in RL

Dynamic programming computes optimal policies by breaking sequential decisions into overlapping subproblems and bootstrapping value estimates using a complete mathematical model of the environment.

Dynamic programming computes exact value functions by executing full Bellman backups across all candidate actions and transition probabilities using complete model dynamics.
Dynamic programming computes exact value functions by executing full Bellman backups across all candidate actions and transition probabilities using complete model dynamics.

Why Does This Exist?

In sequential decision-making, an agent aims to choose actions that maximize cumulative rewards over an extended or infinite horizon. A naive search over all possible future action sequences explodes exponentially with time: an agent planning TT steps ahead with ∣A∣|\mathcal{A}| available actions must evaluate ∣A∣T|\mathcal{A}|^T distinct trajectories. For a modest horizon of T=20T = 20 with 44 actions, this requires comparing over 101210^{12} distinct paths—an intractable combinatorial explosion.

Richard Bellman resolved this dilemma through the Principle of Optimality: an optimal policy has the property that whatever the initial state and initial decision are, the remaining decisions must constitute an optimal policy with regard to the state resulting from the first decision. Because sequential decisions satisfy recursive subproblem overlap, the global optimization problem can be decomposed into local, one-step subproblems.

Dynamic Programming (DP) provides the collection of algorithms that solve Markov Decision Processes by turning Bellman equations into iterative assignment updates. Unlike model-free reinforcement learning methods—such as Temporal Difference Learning or Monte Carlo Methods—which must sample transitions through trial-and-error interaction, DP operates as a planning framework. It assumes a perfect mathematical model of the environment's physics and reward mechanics, executing exhaustive expectations across all possible outcomes. This eliminates sample variance entirely and forms the theoretical backbone for Value Functions and policy optimization.

Think of It Like This

GPS route recalculation with a preloaded city blueprint

Imagine an advanced satellite navigation computer preloaded with a centimeter-accurate offline blueprint of every street, intersection, turn restriction, and statistical traffic delay in a metropolis.

You are sitting at an intersection (ss) wanting the fastest route to the central railway station. The GPS does not need to dispatch physical test vehicles into random alleys to find out where they lead—an approach analogous to trial-and-error model-free exploration. Because it holds the complete map graph and travel cost tables (P(s′∣s,a)P(s' \mid s, a) and R(s,a)R(s, a)), it computes the optimal journey offline.

If the GPS already knows the estimated travel time from every adjacent intersection (s′s') to the destination, recalculating the optimal decision at intersection ss requires looking only one step ahead:

  1. Examine each available street you could take (aa).
  2. Factor in the expected delay on that specific street segment (RR).
  3. Add the precomputed travel duration from the destination intersection onward (V(s′)V(s')).
  4. Select the turn with the minimum total expected time.

By caching and iteratively updating the travel times of neighboring intersections, the system solves the global navigation problem without simulating endless full routes.

Where the analogy stops: Standard road navigation maps are largely deterministic—turning left onto Main Street lands you on Main Street with certainty. In reinforcement learning MDPs, DP explicitly computes expectations over stochastic environments where turning onto an icy street might skid you into several alternative intersections with known probabilities (P(s′∣s,a)P(s' \mid s, a)).

How It Actually Works

Bellman Backup Operators and Mathematical Formulation

Dynamic programming requires complete access to the four-tuple transition dynamics of a finite Markov Decision Process:

  • S\mathcal{S}: The set of all valid states.
  • A\mathcal{A}: The set of all possible actions.
  • P(s′∣s,a)=Pr⁡(St+1=s′∣St=s,At=a)P(s' \mid s, a) = \Pr(S_{t+1} = s' \mid S_t = s, A_t = a): The transition probability kernel.
  • R(s,a,s′)R(s, a, s'): The immediate scalar reward received after transitioning from ss to s′s' under action aa.
  • γ∈[0,1)\gamma \in [0, 1): The discount factor ensuring convergence over infinite horizons.

The expected reward for taking action aa in state ss is formalized as:

R(s,a)=∑s′∈SP(s′∣s,a)R(s,a,s′)R(s, a) = \sum_{s' \in \mathcal{S}} P(s' \mid s, a) R(s, a, s')

The Bootstrap Principle

Dynamic programming updates its value estimates based on other value estimates. Rather than waiting until the end of an episode to compute empirical returns (as in Monte Carlo rollouts), DP performs bootstrapping: it truncates the horizon after a single step and substitutes the current cached value estimate V(s′)V(s') for the remainder of the trajectory.

Bellman Expectation Backup Operator (TπT^\pi)

For a fixed policy π(a∣s)\pi(a \mid s), the value function satisfies the linear system known as the Bellman expectation equation. In DP, this equation is turned into an operational update operator TπT^\pi:

(TπV)(s)≐∑a∈Aπ(a∣s)∑s′∈SP(s′∣s,a)[R(s,a,s′)+γV(s′)](T^\pi V)(s) \doteq \sum_{a \in \mathcal{A}} \pi(a \mid s) \sum_{s' \in \mathcal{S}} P(s' \mid s, a) \left[ R(s, a, s') + \gamma V(s') \right]

Iterative policy evaluation repeatedly applies this operator: Vk+1=TπVkV_{k+1} = T^\pi V_k.

Bellman Optimality Backup Operator (T∗T^*)

To identify the optimal policy π∗\pi^*, the Bellman optimality backup operator T∗T^* performs a greedy maximization over all available actions:

(T∗V)(s)≐max⁡a∈A∑s′∈SP(s′∣s,a)[R(s,a,s′)+γV(s′)](T^* V)(s) \doteq \max_{a \in \mathcal{A}} \sum_{s' \in \mathcal{S}} P(s' \mid s, a) \left[ R(s, a, s') + \gamma V(s') \right]

Equivalently, in terms of state-action values Q(s,a)Q(s, a):

(T∗Q)(s,a)≐∑s′∈SP(s′∣s,a)[R(s,a,s′)+γmax⁡a′∈AQ(s′,a′)](T^* Q)(s, a) \doteq \sum_{s' \in \mathcal{S}} P(s' \mid s, a) \left[ R(s, a, s') + \gamma \max_{a' \in \mathcal{A}} Q(s', a') \right]

Contraction Mapping and Guaranteed Convergence

A critical property of the dynamic programming operators is that both TπT^\pi and T∗T^* are γ\gamma-contraction mappings in the supremum norm (∥V∥∞=max⁡s∈S∣V(s)∣\|V\|_\infty = \max_{s \in \mathcal{S}} |V(s)|):

∥T∗U−T∗V∥∞≤γ∥U−V∥∞for all U,V∈R∣S∣\|T^* U - T^* V\|_\infty \le \gamma \|U - V\|_\infty \quad \text{for all } U, V \in \mathbb{R}^{|\mathcal{S}|}

Because γ<1\gamma < 1, Banach's Fixed-Point Theorem guarantees that:

  1. A unique optimal fixed point V∗V^* exists such that T∗V∗=V∗T^* V^* = V^*.
  2. Repeated application Vk+1=T∗VkV_{k+1} = T^* V_k converges exponentially to V∗V^* from any arbitrary initial vector V0V_0.

Dynamic programming organizes these backups into two canonical algorithmic paradigms:

  • Policy Iteration: Alternates between exhaustive policy evaluation (solving for VπV^\pi) and greedy policy improvement (π′(s)=arg⁡max⁡aQπ(s,a)\pi'(s) = \arg\max_a Q^\pi(s, a)).
  • Value Iteration: Applies the optimality operator T∗T^* directly across all states in each sweep, interleaving policy evaluation and improvement into a single step.

Worked numerical example

Consider an agent in target state ss with discount factor γ=0.9\gamma = 0.9. The agent has two candidate actions: a1a_1 and a2a_2.

The environment dynamics model defines the following branches:

  • Action a1a_1 (stochastic with two successor states s1′s'_1 and s2′s'_2):
    • Transition to s1′s'_1: probability P(s1′∣s,a1)=0.8P(s'_1 \mid s, a_1) = 0.8, immediate reward R(s,a1,s1′)=+2.0R(s, a_1, s'_1) = +2.0, cached estimate Vk(s1′)=10.0V_k(s'_1) = 10.0.
    • Transition to s2′s'_2: probability P(s2′∣s,a1)=0.2P(s'_2 \mid s, a_1) = 0.2, immediate reward R(s,a1,s2′)=+5.0R(s, a_1, s'_2) = +5.0, cached estimate Vk(s2′)=4.0V_k(s'_2) = 4.0.
  • Action a2a_2 (deterministic transition to successor state s3′s'_3):
    • Transition to s3′s'_3: probability P(s3′∣s,a2)=1.0P(s'_3 \mid s, a_2) = 1.0, immediate reward R(s,a2,s3′)=+1.0R(s, a_2, s'_3) = +1.0, cached estimate Vk(s3′)=9.0V_k(s'_3) = 9.0.

Step 1: Compute discounted branch targets for action a1a_1

For each successor state s′s', compute the one-step target R(s,a1,s′)+γVk(s′)R(s, a_1, s') + \gamma V_k(s'):

  • Target for branch s1′s'_1: G1=2.0+0.9×10.0=2.0+9.0=11.0G_1 = 2.0 + 0.9 \times 10.0 = 2.0 + 9.0 = 11.0
  • Target for branch s2′s'_2: G2=5.0+0.9×4.0=5.0+3.6=8.6G_2 = 5.0 + 0.9 \times 4.0 = 5.0 + 3.6 = 8.6

Step 2: Compute the expectation for action a1a_1 (Action-Value Q(s,a1)Q(s, a_1))

Apply the probability-weighted Bellman expectation across both branches: Qk(s,a1)=P(s1′∣s,a1)⋅G1+P(s2′∣s,a1)⋅G2Q_k(s, a_1) = P(s'_1 \mid s, a_1) \cdot G_1 + P(s'_2 \mid s, a_1) \cdot G_2 Qk(s,a1)=(0.8×11.0)+(0.2×8.6)=8.80+1.72=10.52Q_k(s, a_1) = (0.8 \times 11.0) + (0.2 \times 8.6) = 8.80 + 1.72 = 10.52

Step 3: Compute the expectation for action a2a_2 (Action-Value Q(s,a2)Q(s, a_2))

Compute the one-step target for the deterministic branch: Qk(s,a2)=1.0×[1.0+0.9×9.0]=1.0+8.10=9.10Q_k(s, a_2) = 1.0 \times \left[ 1.0 + 0.9 \times 9.0 \right] = 1.0 + 8.10 = 9.10

Step 4: Apply the Bellman Optimality Backup Operator

The Bellman optimality operator selects the maximum value among all actions: Vk+1(s)=max⁡{Qk(s,a1),Qk(s,a2)}=max⁡{10.52,9.10}=10.52V_{k+1}(s) = \max \left\{ Q_k(s, a_1), Q_k(s, a_2) \right\} = \max \{ 10.52, 9.10 \} = 10.52

The derived greedy policy for state ss selects: πk+1(s)=arg⁡max⁡aQk(s,a)=a1\pi_{k+1}(s) = \arg\max_{a} Q_k(s, a) = a_1

The value of state ss has been updated from its prior value to 10.5210.52 purely by backing up expectations from cached successor state estimates and the known transition model.

Code

from dataclasses import dataclassfrom typing import Dict, List, Tuple

@dataclass(frozen=True)class Transition:    """Represents a stochastic branch in the environment dynamics model."""    prob: float        # P(s' | s, a)    next_state: str    # Successor state s'    reward: float      # Immediate reward R(s, a, s')

class ModelMDP:    """Tabular Markov Decision Process solved via Dynamic Programming backups."""
    def __init__(        self,        states: List[str],        actions: List[str],        transitions: Dict[Tuple[str, str], List[Transition]],        gamma: float = 0.9,    ) -> None:        self.states = states        self.actions = actions        self.transitions = transitions        self.gamma = gamma
    def compute_q_value(        self, state: str, action: str, value_function: Dict[str, float]    ) -> float:        """        Computes Q(s, a) using the full Bellman expectation backup:        Q(s, a) = sum_{s'} P(s'|s,a) * [ R(s,a,s') + gamma * V(s') ]        """        branches = self.transitions.get((state, action), [])        if not branches:            return 0.0
        q_val = 0.0        for branch in branches:            discounted_future = self.gamma * value_function[branch.next_state]            q_val += branch.prob * (branch.reward + discounted_future)        return q_val
    def bellman_optimality_backup(        self, state: str, value_function: Dict[str, float]    ) -> Tuple[float, str]:        """        Executes a 1-step Bellman Optimality Backup for a single state:        V(s) <- max_a Q(s, a)        """        action_values = {            action: self.compute_q_value(state, action, value_function)            for action in self.actions        }        best_action = max(action_values, key=lambda a: action_values[a])        best_value = action_values[best_action]        return best_value, best_action
    def full_sweep_backup(        self, value_function: Dict[str, float]    ) -> Tuple[Dict[str, float], Dict[str, str]]:        """Performs one synchronous dynamic programming sweep across all states."""        new_v: Dict[str, float] = {}        greedy_policy: Dict[str, str] = {}
        for s in self.states:            v_val, best_act = self.bellman_optimality_backup(s, value_function)            new_v[s] = round(v_val, 4)            greedy_policy[s] = best_act
        return new_v, greedy_policy

if __name__ == "__main__":    # Define states and actions matching the worked numerical example    states = ["s", "s'_1", "s'_2", "s'_3"]    actions = ["action_a", "action_b"]
    transitions = {        ("s", "action_a"): [            Transition(prob=0.8, next_state="s'_1", reward=2.0),            Transition(prob=0.2, next_state="s'_2", reward=5.0),        ],        ("s", "action_b"): [            Transition(prob=1.0, next_state="s'_3", reward=1.0),        ],        # Terminal successor states with zero rewards        ("s'_1", "action_a"): [Transition(prob=1.0, next_state="s'_1", reward=0.0)],        ("s'_1", "action_b"): [Transition(prob=1.0, next_state="s'_1", reward=0.0)],        ("s'_2", "action_a"): [Transition(prob=1.0, next_state="s'_2", reward=0.0)],        ("s'_2", "action_b"): [Transition(prob=1.0, next_state="s'_2", reward=0.0)],        ("s'_3", "action_a"): [Transition(prob=1.0, next_state="s'_3", reward=0.0)],        ("s'_3", "action_b"): [Transition(prob=1.0, next_state="s'_3", reward=0.0)],    }
    # Initial cached state-value estimates V_k    v_k = {        "s": 0.0,        "s'_1": 10.0,        "s'_2": 4.0,        "s'_3": 9.0,    }
    env = ModelMDP(states, actions, transitions, gamma=0.9)
    print("--- 1-Step DP Bellman Backup for State 's' ---")    for act in actions:        q = env.compute_q_value("s", act, v_k)        print(f"Q(s, {act}) = {q:.4f}")
    new_v_s, best_action = env.bellman_optimality_backup("s", v_k)    print(f"\nUpdated Value V_{{k+1}}(s) = {new_v_s:.4f}")    print(f"Greedy Policy pi_{{k+1}}(s)   = '{best_action}'")
    # Perform a full-sweep backup across the entire state space    updated_v, greedy_policy = env.full_sweep_backup(v_k)    print("\nFull-sweep updated values:", updated_v)    print("Derived greedy policy:   ", greedy_policy)
    # Expected Output:    # --- 1-Step DP Bellman Backup for State 's' ---    # Q(s, action_a) = 10.5200    # Q(s, action_b) = 9.1000    #    # Updated Value V_{k+1}(s) = 10.5200    # Greedy Policy pi_{k+1}(s)   = 'action_a'    #    # Full-sweep updated values: {'s': 10.52, "s'_1": 9.0, "s'_2": 3.6, "s'_3": 8.1}    # Derived greedy policy:    {'s': 'action_a', "s'_1": 'action_a', "s'_2": 'action_a', "s'_3": 'action_a'}

Watch Out For

The fatal dependency on a perfect environment model

Dynamic programming is fundamentally a planning algorithm, not a learning algorithm. Its Bellman backup equations require computing the exact mathematical expectation ∑s′P(s′∣s,a)[… ]\sum_{s'} P(s' \mid s, a) [\dots]. If transition probabilities P(s′∣s,a)P(s' \mid s, a) or reward dynamics R(s,a,s′)R(s, a, s') are unknown, noisy, or non-stationary, pure DP cannot execute a single step.

In practical real-world problems like robotics or stock trading, the environment dynamics cannot be written down in closed form. Practitioners attempting to apply DP in model-free settings face total failure.

The Fix: When the environment is accessible only via interaction samples (s,a,r,s′)(s, a, r, s'), use model-free reinforcement learning methods such as Temporal Difference Learning (Q-learning, SARSA) or Monte Carlo Methods. Alternatively, apply Model-Based RL to learn an empirical transition model P^(s′∣s,a)\hat{P}(s' \mid s, a) before executing DP planning backups.

The curse of dimensionality and state space explosion

A synchronous DP sweep iterates over every state s∈Ss \in \mathcal{S} and evaluates every action a∈Aa \in \mathcal{A} across all possible transitions s′s'. For dense transition models, the computational complexity per sweep is O(∣S∣2∣A∣)\mathcal{O}(|\mathcal{S}|^2 |\mathcal{A}|).

While polynomial in the number of states, ∣S∣|\mathcal{S}| grows exponentially with the number of state variables—a barrier Bellman termed the "curse of dimensionality." A robotic arm with 10 continuous joint angles or a game with 30 discrete state attributes has an astronomical state space that exhausts memory and renders tabular DP sweeps impossible.

The Fix: Transition from tabular dynamic programming to Approximate Dynamic Programming (ADP). Replace tabular state arrays with parametric function approximation (such as deep neural networks in Deep Q-Networks and Actor-Critic architectures), or employ asynchronous updates and sample-based tree search (such as Monte Carlo Tree Search) to focus Bellman backups only on trajectories visited by the agent.

The Quick Version

  • Planning with a perfect model: Dynamic programming solves MDPs by computing exact expectations over fully known transition distributions P(s′∣s,a)P(s' \mid s, a) and reward functions R(s,a)R(s, a), eliminating sample variance.
  • Bootstrapping via contraction mappings: It avoids exponential trajectory rollouts by recursively updating current state values using cached values of successor states via contraction operators (TπT^\pi and T∗T^*), guaranteeing unique convergence.
  • Exhaustive backup tree: Unlike sample-based RL which samples single transitions (s,a,r,s′)(s, a, r, s'), DP performs full tree backups across every action and every stochastic successor state.
  • Foundation of modern RL: While tabular DP suffers from the curse of dimensionality in large state spaces, its Bellman backup formulations serve as the mathematical bedrock for policy iteration, value iteration, and deep reinforcement learning.