In TD(0), the target for updating was one real reward plus a bootstrap: . In Monte Carlo, the target was the complete return—every reward until the episode ends. Nobody said you have to choose one of those two. You can collect two real rewards before bootstrapping. Or three. Or ten.
That single observation gives us n-step TD, and with it, a tunable dial between TD and MC:
Each card uses more real reward and less estimated value. By the end of this page you’ll know how to implement any point on this dial—and you’ll see measured evidence that the middle of the dial learns fastest.
The n-step Return
Think of the value target as a relay race between experience and estimation. Experience runs the first leg: it contributes real, observed rewards. Then estimation takes the baton: the value estimate of the state you reached summarizes everything after that.
- With , estimation carries almost the whole race—fast to compute, but you inherit whatever errors your estimates have.
- With large, experience carries almost everything—no estimation errors, but you’re summing many noisy rewards and waiting many steps.
The n-step return is simply “n legs of experience, then hand off to the estimate.”
The n-step return is the sum of the next discounted rewards, plus the discounted value estimate of the state reached after those steps. It becomes the update target for in n-step TD.
Recall the book’s return convention: , where is the reward for the transition out of . Truncating after rewards and correcting with a value estimate gives:
Boundary conventions:
- If the episode terminates before steps are available (), the n-step return is just the ordinary full return: . There’s no state left to bootstrap from.
- recovers the TD(0) target: .
- for every recovers Monte Carlo: every target is a full return.
The n-step TD update then looks exactly like every update you’ve seen:
One theoretical reassurance, stated without proof: n-step returns satisfy an error-reduction property. The worst-case error of the expected n-step return, relative to the true , is at most times the worst-case error of the current estimates. Because (and shrinks as grows), updating toward n-step returns pulls estimates toward the truth—this is the core of the convergence argument for all n-step methods.
A 3-step return, by hand
An agent experiences: with rewards , , . Suppose and the current estimate is .
The 3-step return for :
- Real rewards:
- Bootstrap:
- Total:
Compare the 1-step return from the same position: —one real reward and heavy reliance on the estimate . The 3-step return replaced two steps of estimation with two steps of reality.
The Algorithm: Learning n Steps Behind
There’s one wrinkle in turning this into an algorithm: at time , the n-step return for isn’t computable yet—you have to wait until time to have seen the rewards. So n-step TD always updates the state visited steps ago. When the episode ends, the last few states get their (shortened) updates in a final flush.
Algorithm: n-step TD for Estimating V^π
────────────────────────────────────────
Input: policy π, step size α, discount γ, steps n
Initialize V(s) arbitrarily
Loop for each episode:
Store S_0; T ← ∞
For t = 0, 1, 2, ...:
If t < T:
Take action from π, observe and store R_{t+1}, S_{t+1}
If S_{t+1} is terminal: T ← t + 1
τ ← t − n + 1 (τ is the time whose state gets updated)
If τ ≥ 0:
G ← sum of γ^(i−τ−1) R_i for i = τ+1 ... min(τ+n, T)
If τ + n < T: G ← G + γ^n V(S_{τ+n})
V(S_τ) ← V(S_τ) + α [G − V(S_τ)]
Until τ = T − 1
The update lag is the price of looking ahead: n-step TD is still fully online and incremental, but each state’s update arrives steps late (and requires buffering the last rewards and states). TD(0) has zero lag; Monte Carlo’s “lag” is the whole episode. This tradeoff—target quality versus update delay—shows up again when we fix it elegantly with eligibility traces.
Here is a complete, runnable implementation—the same code used for the experiment below. First the environment: a 19-state random walk, the big sibling of the 5-state walk from TD(0) Prediction.
import numpy as np
# 19-state random walk (Sutton & Barto, Fig 7.2 setup):
# non-terminal states 1..19, terminals at 0 and 20, start at 10.
# Stepping into 0 gives reward -1; into 20 gives +1; else 0. gamma = 1.
N, START, GAMMA = 19, 10, 1.0
TRUE_V = np.arange(-20, 22, 2) / 20.0 # true values: v(s) = s/10 - 1
TRUE_V[0] = TRUE_V[20] = 0.0 # terminals
def gen_episode(rng):
"""Return (states, rewards); rewards[t] is for states[t] -> states[t+1]."""
states, rewards, s = [START], [], START
while 0 < s < 20:
s2 = s + (1 if rng.random() < 0.5 else -1)
rewards.append(1.0 if s2 == 20 else (-1.0 if s2 == 0 else 0.0))
states.append(s2)
s = s2
return states, rewardsAnd n-step TD prediction. Because the policy here is fixed (pure chance), we can generate the episode first and then apply the updates in the same order the online algorithm would—time τ order, always bootstrapping from the current values:
def nstep_td_run(n, alpha, episodes, rng):
"""Online n-step TD prediction. Returns RMS error after each episode."""
V = np.zeros(21)
errs = []
for _ in range(episodes):
states, rewards = gen_episode(rng)
T = len(rewards)
for tau in range(T): # update time for S_tau
end = min(tau + n, T)
G = sum(GAMMA ** (k - tau) * rewards[k] for k in range(tau, end))
if tau + n < T: # bootstrap unless past the end
G += GAMMA ** n * V[states[tau + n]]
V[states[tau]] += alpha * (G - V[states[tau]])
errs.append(np.sqrt(np.mean((V[1:20] - TRUE_V[1:20]) ** 2)))
return errsNote the two conventions in code form: the bootstrap is skipped when (the return is then the plain full return), and terminal states carry value 0.
The Experiment: Which n Wins?
Time to settle it with data. The task: estimate the value function of the 19-state random walk under the random policy, starting from everywhere.
Why 19 states instead of 5? Room to maneuver. With longer episodes (about 101 steps on average, measured over 20,000 episodes), there’s space for multi-step returns to show their strength—and for very deep returns to show their weakness.
Protocol (following Sutton & Barto’s Figure 7.2): for each and each , run 10 episodes, compute the RMS error between and the true values (averaged over the 19 states and the 10 episodes), and average that over 100 independent runs. Identical episode sequences are used for every pair, so differences are purely algorithmic. The measured results:
| n | Best α | Min avg RMS error |
|---|---|---|
| 1 | 0.82 | 0.348 |
| 2 | 0.58 | 0.280 |
| 4 | 0.38 | 0.267 |
| 8 | 0.24 | 0.281 |
| 16 | 0.14 | 0.313 |
| 32 | 0.08 | 0.361 |
The shape is a clean U: error falls from to , then climbs again. The intermediate methods—neither TD(0) nor Monte Carlo—learn fastest. Exactly the result Sutton & Barto report, reproduced here from scratch.
Two further patterns in the measured data are worth noticing:
The best step size shrinks as n grows. Best α drops from 0.82 at to 0.08 at . That’s the variance talking: a 32-step return is a sum of many random rewards, so each individual target is noisy, and the safe response is to take smaller steps and average over more of them. A low-variance 1-step target can be trusted with big steps.
Each n fails differently. Look at the full sweep: at , deep returns are fine ( scores 0.324) while lags at 0.506—with small steps, TD(0)‘s bias fades too slowly over just 10 episodes. At , the ranking flips: scores 0.390 while blows up to 0.711—big steps amplify the deep returns’ noise. The intermediate n’s are the only ones that are decent everywhere, which is why they win the overall sweep.
| n | α = 0.1 | α = 0.2 | α = 0.4 | α = 0.6 | α = 0.8 | α = 1.0 |
|---|---|---|---|---|---|---|
| 1 | 0.506 | 0.471 | 0.413 | 0.369 | 0.348 | 0.390 |
| 2 | 0.470 | 0.407 | 0.313 | 0.280 | 0.320 | 0.442 |
| 4 | 0.413 | 0.325 | 0.268 | 0.312 | 0.390 | 0.515 |
| 8 | 0.349 | 0.283 | 0.322 | 0.398 | 0.479 | 0.586 |
| 16 | 0.324 | 0.327 | 0.417 | 0.495 | 0.564 | 0.643 |
| 32 | 0.364 | 0.423 | 0.535 | 0.607 | 0.660 | 0.711 |
The winning n is not a universal constant—it depends on the problem and the horizon. This experiment measures error over the first 10 episodes, where fast credit propagation matters most; longer training, shorter episodes, or less reward noise all shift the sweet spot. The robust lesson is the U-shape itself: some intermediate n beats both extremes, so treat n as a hyperparameter worth a small sweep rather than defaulting to 1.
n-step SARSA: The Same Dial for Control
Everything above was prediction. To get a control method, do exactly what SARSA did to TD(0): switch from state values to action values and follow an ε-greedy policy. The n-step return simply ends in instead of , and the update targets —everything else, including the n-step lag and the end-of-episode flush, carries over unchanged. Deeper backups pay off in control for the same reason they did in prediction, and often more visibly: when rewards are sparse, a single successful episode updates the last state-action pairs along the path instead of only the final one, so good news spreads times faster through the Q-table.
def nstep_sarsa_episode(env, Q, n, alpha=0.1, gamma=1.0, eps=0.1, rng=None):
"""One episode of on-policy n-step SARSA (tabular)."""
rng = rng or np.random.default_rng()
def eps_greedy(s):
if rng.random() < eps:
return rng.integers(len(Q[s]))
return int(np.argmax(Q[s]))
s = env.reset()
a = eps_greedy(s)
S, A, R = [s], [a], [0.0] # R[0] unused; R[t+1] rewards transition t
T, t = float('inf'), 0
while True:
if t < T:
s2, r, done = env.step(A[t])
S.append(s2); R.append(r)
if done:
T = t + 1
else:
A.append(eps_greedy(s2))
tau = t - n + 1
if tau >= 0:
end = int(min(tau + n, T))
G = sum(gamma ** (i - tau - 1) * R[i] for i in range(tau + 1, end + 1))
if tau + n < T:
G += gamma ** n * Q[S[tau + n]][A[tau + n]]
Q[S[tau]][A[tau]] += alpha * (G - Q[S[tau]][A[tau]])
if tau == T - 1:
break
t += 1Try this on CliffWalking from the SARSA vs Q-learning comparison with —the multi-step versions typically discover the safe path in noticeably fewer episodes.
Deep Dive▶
What about off-policy n-step control? Q-learning’s trick—bootstrapping from —only “corrects” the final step of the target. With an n-step return, the intermediate actions were chosen by the behavior policy, and if that differs from the target policy, the multi-step return is answering the wrong question. Fixing this properly needs importance sampling ratios (reweighting by how likely the target policy would have been to take those actions) or smarter constructions like tree-backup updates that bootstrap over the un-taken actions at every step. We’ll flag this issue again in the unifying view—modern deep RL systems often just use small n and ignore the mismatch.
Summary
The n-step return generalizes both of our target constructions: real rewards, then a bootstrap. Measured on the 19-state random walk, intermediate depths win— achieved RMS error 0.267 versus 0.348 for TD(0) and 0.361 for the deepest setting tested—because moderate n dilutes bootstrap bias without absorbing the full variance of a long return. The same dial converts to control as n-step SARSA.
But n-step methods force a discrete choice: one n for every state, every update, plus an n-step buffer and update lag. The next section asks a better question: why pick one n at all? Averaging all n-step returns—with a clever incremental implementation—gives us TD(λ) and eligibility traces.