n-step TD left us with a nagging inefficiency: we measured that beats and on the random walk—but we only learned that by running all of them. Every n-step return is a legitimate estimate of the same quantity. Why should one update use exactly one of them and throw the rest away?
It doesn’t have to. This section develops the two central ideas:
- The forward view: define a target that averages all n-step returns at once, weighted geometrically by a parameter . This is the λ-return.
- The backward view: compute that averaging online, one cheap update per step, using a fading memory called an eligibility trace. This is the TD(λ) algorithm.
The punchline—kept precise throughout, and consistent with how we introduced it back in TD(0) Prediction: is a trace-decay parameter, not a step count. Setting recovers one-step TD; setting recovers (every-visit) Monte Carlo.
The Forward View: The λ-Return
Instead of betting on one n, place a spread of bets. The λ-return mixes the 1-step return, the 2-step return, the 3-step return, and so on—each weighted a factor less than the previous. With , the 1-step return gets weight 0.10, the 2-step gets 0.09, the 3-step 0.081, and the weights keep shrinking geometrically.
One dial, , now controls the effective depth of the target. Small λ concentrates weight on shallow returns (TD-like); λ near 1 spreads weight far into the future (MC-like). And unlike n, which lurches from 1 to 2 to 3, λ moves the mixture smoothly.
Here are those weights, computed for :
The λ-return is the weighted average of all n-step returns from time , where the n-step return receives weight . The trace-decay parameter sets how quickly the weights fade with depth.
The λ-return:
The prefactor exists to make the weights sum to one—it normalizes the geometric series:
So is a proper weighted average of legitimate targets, hence itself a legitimate target.
In an episodic task, every n-step return with equals the full return . Collecting all that tail weight into one term gives the practical form:
The boundary cases now follow by inspection:
- λ = 0: only the term survives (using ), so — the TD(0) target. That is why the algorithm is called TD(0).
- λ = 1: every finite-n weight vanishes and the tail weight becomes 1, so — the full Monte Carlo return.
The forward-view algorithm (“offline λ-return algorithm”) updates every visited state toward its λ-return at episode end:
Keep the two dials distinct: n is an integer that selects one backup depth; λ is a decay rate that mixes all depths. TD(0) still uses one real reward—the “0” refers to λ, not to how many rewards are collected. This is exactly the framing we flagged in TD(0) Prediction, now with the machinery to back it up.
There’s an obvious practical problem, though. The λ-return for time depends on n-step returns of every depth—which means it isn’t fully known until the episode ends. Written this way, the λ-return algorithm is Monte Carlo-like in the worst way: theoretically clean, but offline and acausal. We seem to have lost TD’s best feature.
The Backward View: Eligibility Traces
The fix is one of the most elegant ideas in RL: flip the direction of time.
The forward view stands at state looking ahead: “which future returns should update me?” The backward view stands at the current TD error looking behind: “which past states should I update?”
Picture every state carrying a glowing ember. When the agent visits a state, its ember flares up. Every step afterward, all embers fade by a factor . Now, whenever a TD error occurs—a moment of surprise—every state gets updated in proportion to how brightly its ember is still glowing.
The ember is the eligibility trace. It implements two credit-assignment heuristics at once:
- Recency: states visited recently glow brighter, so they absorb more of the blame or credit for the current surprise.
- Frequency: states visited repeatedly have had their embers bumped multiple times, so they’re extra eligible.
The magic (made precise below): fading embers by per step distributes each TD error backward in exactly the pattern needed to reproduce λ-return learning.
The eligibility trace is a per-state memory that decays by every step and increments by 1 whenever is visited: , where is 1 if the condition holds and 0 otherwise. Traces start at zero at the beginning of each episode.
TD(λ) with accumulating traces. At each step, compute the ordinary one-step TD error:
then update all states at once, each in proportion to its trace:
Per step this costs one TD error plus one sweep over the trace vector—O(number of states), independent of episode length and of λ. No stored rewards, no n-step buffer, no waiting: TD(λ) is exactly as online as TD(0).
Algorithm: Tabular TD(λ), accumulating traces
─────────────────────────────────────────────
Input: policy π, step size α, discount γ, trace decay λ
Initialize V(s) arbitrarily
Loop for each episode:
e(s) ← 0 for all s
Initialize S
Loop for each step:
Take action from π, observe R, S'
δ ← R + γV(S') − V(S) (V(S') = 0 if S' terminal)
e(s) ← γλ e(s) for all s (all traces fade)
e(S) ← e(S) + 1 (current state flares up)
V(s) ← V(s) + α δ e(s) for all s
S ← S'
until S is terminal
The full implementation, and the one used for the measurements below:
import numpy as np
# Same 19-state random walk as the n-step experiment:
# states 1..19, terminals 0 and 20, start at 10, rewards -1/+1 at the ends.
N, START, GAMMA = 19, 10, 1.0
TRUE_V = np.arange(-20, 22, 2) / 20.0
TRUE_V[0] = TRUE_V[20] = 0.0
def gen_episode(rng):
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, rewards
def td_lambda_run(lam, alpha, episodes, rng):
"""Online TD(lambda), accumulating traces. RMS error after each episode."""
V = np.zeros(21)
errs = []
for _ in range(episodes):
states, rewards = gen_episode(rng)
e = np.zeros(21) # traces reset each episode
for t in range(len(rewards)):
s, s2, r = states[t], states[t + 1], rewards[t]
delta = r + GAMMA * V[s2] - V[s] # one ordinary TD error
e *= GAMMA * lam # all traces fade
e[s] += 1.0 # current state becomes eligible
V += alpha * delta * e # one error updates many states
errs.append(np.sqrt(np.mean((V[1:20] - TRUE_V[1:20]) ** 2)))
return errsNote what the inner loop does not contain: no n-step returns, no lookahead, no stored episode. Four lines convert TD(0) into the entire TD-to-MC spectrum.
Measured: TD(λ) on the Random Walk
Same protocol as the n-step experiment—average RMS error over the 19 states and the first 10 episodes, averaged over 100 runs, sweeping from 0.02 to 1.0 for each λ. The measured results:
| λ | Best α | Min avg RMS error |
|---|---|---|
| 0.0 | 0.82 | 0.348 |
| 0.4 | 0.68 | 0.276 |
| 0.8 | 0.32 | 0.266 |
| 0.9 | 0.20 | 0.294 |
| 0.95 | 0.12 | 0.334 |
| 1.0 | 0.02 | 0.508 |
The same U-shape as the n sweep, for the same bias-variance reason—and the numbers line up beautifully with the n-step results:
- λ = 0 exactly reproduces 1-step TD: best α of 0.82 and RMS 0.348, identical to the row of the n-step sweep. That’s not a coincidence; it’s the boundary case, confirmed numerically.
- The winner, λ = 0.8 (RMS 0.266), matches the best n-step result (, RMS 0.267). With , over half the λ-return’s weight falls on depths 4 and shallower, with the rest spreading deeper—an averaged version of the same intermediate depth.
- λ = 1 is the worst setting tested (RMS 0.508 at a tiny best α of 0.02), and it’s fragile: with accumulating traces it diverged outright for every α of 0.32 and above in this sweep—value estimates ran away to floating-point overflow—and even at α = 0.1 the average error exploded to about 167. Monte Carlo-like variance plus traces that can grow past 1 on revisits is an explosive combination. λ = 1 is a conceptual endpoint, not a practical setting.
The pattern generalizes: λ in the 0.8–0.95 range is a strong default across many tabular problems, with smaller best step sizes as λ grows (same variance logic as with growing n). And as with n, the exact winner depends on the problem and horizon—the robust fact is that intermediate λ beats both endpoints.
Why the Two Views Agree
We introduced traces with a metaphor. Here’s the actual claim, and a numerical verification of it.
Follow one visit to a state at time . Its trace starts at 1 and fades: after one step, after two, and so on. So over the rest of the episode, the backward view gives a total update proportional to — every future surprise, discounted by how much the trace has faded.
Meanwhile, the forward view wants to move by . Expand that error and it telescopes into exactly the same discounted sum of TD errors. Each successive TD error is precisely the correction that upgrades an n-step return to an (n+1)-step return, and the geometric λ-weights of the forward view become the geometric fading of the trace. Same quantity, computed in opposite time directions.
The key identity: if is held fixed during the episode, the λ-return error decomposes as
Sketch for the λ = 1 case (the general case just carries λ through): repeatedly apply , which follows from adding and subtracting inside . Unrolling to the end of the episode telescopes the MC error into .
Summing these per-state errors over all visits and exchanging the order of summation shows: the total increments produced by the forward view over an episode equal the total increments produced by the backward view—provided updates are applied offline (accumulated during the episode, applied at the end, so that stays fixed while the deltas are computed).
Don’t take the algebra’s word for it. This check computes both views’ summed increments on the same episodes with the same frozen , and compares:
def offline_forward_increments(states, rewards, V, lam, alpha):
"""Summed lambda-return updates for one episode (V frozen)."""
T, inc = len(rewards), np.zeros(21)
for t in range(T):
G_lam = 0.0
for n in range(1, T - t): # truncated n-step returns
G = sum(GAMMA ** k * rewards[t + k] for k in range(n))
G += GAMMA ** n * V[states[t + n]]
G_lam += (1 - lam) * lam ** (n - 1) * G
G_full = sum(GAMMA ** k * rewards[t + k] for k in range(T - t))
G_lam += lam ** (T - t - 1) * G_full # tail weight on full return
inc[states[t]] += alpha * (G_lam - V[states[t]])
return inc
def offline_backward_increments(states, rewards, V, lam, alpha):
"""Summed eligibility-trace updates for one episode (V frozen)."""
inc, e = np.zeros(21), np.zeros(21)
for t in range(len(rewards)):
s, s2, r = states[t], states[t + 1], rewards[t]
delta = r + GAMMA * V[s2] - V[s]
e *= GAMMA * lam
e[s] += 1.0
inc += alpha * delta * e
return incRunning both on 50 random-walk episodes with randomized value functions, for each , the measured maximum absolute difference between the two increment vectors is 5.3 × 10⁻¹⁵—zero, up to floating-point round-off. The equivalence is exact.
The equivalence is exact for offline updates only. The practical TD(λ) algorithm above applies updates online, changing mid-episode, so it only approximates the λ-return algorithm—an excellent approximation for reasonable α, and often actually better, since online updates use fresher values. If you want exact online equivalence, it exists: the true online TD(λ) algorithm achieves it with a modified (“dutch”) trace, at modest extra cost. We leave its derivation to Sutton & Barto (Chapter 12).
The Endpoints, Precisely
We can now state the family relationships exactly—these are the claims previewed in TD(0) Prediction and TD vs Monte Carlo, now proven and measured:
Note the fine print on the MC side: it’s every-visit MC (each visit to a state gets an update), not first-visit—and the equivalence is for offline updating. Online TD(1) is a slightly different, and as we measured, fragile algorithm.
Deep Dive▶
Trace variants. The accumulating trace is the classic, but not the only choice:
- Replacing traces reset the trace to 1 on a visit () instead of adding 1. This caps traces at 1, tames the divergence we measured at high λ with large α, and historically often worked better on problems with frequent revisits.
- Dutch traces ( for the visited state) arise naturally in the derivation of true online TD(λ) and are the theoretically preferred modern choice.
For linear function approximation, the trace becomes a vector over parameters rather than states——the same fading-memory construction addressed to whatever is doing the predicting. That generalization is where function approximation will pick up the thread.
Summary
The λ-return averages all n-step returns with geometric weights —the forward view. Eligibility traces compute the same learning backward in time: traces fade by , flare on visits, and each one-step TD error updates all traced states at once—online, O(states) per step, no waiting. The two views produce identical total updates offline (verified to 10⁻¹⁵ here), and the endpoints are exact: λ = 0 is one-step TD, offline λ = 1 is every-visit Monte Carlo. Measured on the random walk, intermediate λ wins—λ = 0.8 hit RMS 0.266, matching the best n-step method, while λ = 1 was both worst and prone to divergence.
You’ve now seen the complete bridge between TD and Monte Carlo, built twice—once with n, once with λ. The final section zooms out: one map for every method so far, and the direct line from this chapter to Rainbow and GAE.