Reinforcement Learning · Control
📅 July 2026 ⏱ ≈ 12 min read 🎯 Advanced · Last updated: 9 July 2026

Q-learning in continuous state spaces: from lookup tables to Deep Q-Networks

Classic Q-learning stores one number per (state, action) pair in a table — fine for a 5×5 gridworld, hopeless for a cart-pole (a classic control benchmark: a pole balancing upright on a moving cart) with a 4-dimensional continuous state or a robot arm with continuous joint angles. The fix is to replace the table with a function approximator, and the story of how that idea was made to work reliably is the story of Deep Q-Networks.

TL;DR: This article explains why standard Q-learning, which stores one value per table cell, breaks down for continuous states such as position or velocity, and how replacing the table with a function approximator fixes it. It walks through tile coding, linear approximation, and Deep Q-Networks, along with the experience replay buffer and target network tricks that keep DQN training stable, ending with real applications.

Why tabular Q-learning breaks

The classic Q-learning update refines a table Q(s, a) toward the observed reward plus the best achievable future value:

Q(s, a) ← Q(s, a) + α [ r + γ·max_a' Q(s', a') − Q(s, a) ]
α = learning rate, γ = discount factor, s' = next state

This requires visiting every (s, a) pair many times to get an accurate estimate. When the state is continuous — position, velocity, joint angle — there are infinitely many states, so no state is ever visited twice, and a table can never converge. Worse, a table gives you zero generalization: learning about state (1.001, 0.5) tells you nothing about the nearly identical state (1.002, 0.5).

Tile coding: discretizing space cheaply

A classic, cheap fix is tile coding: overlay several offset grids ("tilings") on the continuous state space. Each tiling activates exactly one tile per state, and the state's feature vector is the concatenation of one-hot tile indicators across all tilings. Nearby states share most of their active tiles, which gives coarse but effective generalization for free.

Why multiple offset tilings? A single grid gives blocky, discontinuous value functions. Overlapping several grids, each shifted by a fraction of a tile width, lets nearby states share most of their tiles while still resolving fine detail — a cheap approximation to a smooth kernel.

Linear function approximation

With a feature vector φ(s, a) from tile coding (or any hand engineered basis), Q is approximated as a linear combination of weights:

Q(s, a; w) = wᵀφ(s, a)
w ← w + α [ r + γ·max_a' Q(s', a'; w) − Q(s, a; w) ] · φ(s, a)
Gradient of a linear function is just the feature vector itself

This is exactly the same TD-error rule as tabular Q-learning, just applied to weights instead of table cells — and it was the state-of-the-art for continuous control (with tile coding, RBF, or Fourier bases) for decades before deep learning.

Deep Q-Networks: a neural Q-function

Deep Q-Networks (Mnih et al., 2015) replace the hand-designed feature vector with a neural network Q(s, a; θ) that learns its own features end to end from raw state input (pixels, joint angles, etc.). The TD-error becomes a squared-error loss minimized by gradient descent:

L(θ) = E[ ( r + γ·max_a' Q(s', a'; θ⁻) − Q(s, a; θ) )² ]
θ⁻ = target network parameters, held fixed for many steps (see below)

In principle this is "just" Q-learning with gradient descent instead of a per-weight update rule. In practice, plugging a neural network directly into the naive Q-learning loop diverges almost immediately — two engineering tricks were needed to make it stable.

Replay buffers and target networks

ProblemFixWhy it works
Correlated dataExperience replay bufferSample random past transitions instead of consecutive ones, breaking temporal correlation that violates the i.i.d. assumption behind SGD
Moving targetSeparate target network θ⁻Freeze the bootstrap target for thousands of steps so the network isn't chasing a target that shifts every update
Overestimation biasDouble DQNSelect the best action with the online network, evaluate its value with the target network — decouples selection from evaluation
Reward scale varianceReward clipping / normalizationKeeps gradients in a consistent range across environments with wildly different reward magnitudes

Without a replay buffer and target network, the Q-function chases its own tail: every gradient step changes the bootstrap target itself, and the whole system can oscillate or diverge instead of converging.

A minimal DQN update in JavaScript

function dqnUpdate(onlineNet, targetNet, batch, gamma = 0.99) {
  let loss = 0;
  for (const { s, a, r, sNext, done } of batch) {
    const qValues = onlineNet.forward(s);
    const qNextTarget = done ? 0 : Math.max(...targetNet.forward(sNext));
    const tdTarget = r + gamma * qNextTarget;
    const tdError = tdTarget - qValues[a];

    loss += tdError * tdError;
    onlineNet.backwardStep(s, a, tdError); // gradient step on Q(s,a) only
  }

  // Periodically hard-copy (or soft-average with Polyak update) online → target
  if (onlineNet.step % 1000 === 0) targetNet.copyFrom(onlineNet);
  return loss / batch.length;
}
Exploration: continuous-state Q-learning still needs explicit exploration, most commonly ε-greedy (random action with probability ε, decayed over training) — the neural network alone gives you no exploration for free.

Where continuous Q-learning is used

▶ Live Demo

🤖 Explore reinforcement learning live

Watch an agent learn a policy through trial, error, and reward — from tabular Q-tables to function approximation

Open simulation →

🔗 Related Simulations

🤖Reinforcement Learning 🧠Neural Network Training 🌐Neural Network 🎲Markov Chains