In Lesson 3 you fused two measurements taken at the same instant. The Kalman filter does the same inverse-variance fuse — but recursively, every tick, blending a model-based prediction with a fresh measurement, each weighted by its uncertainty. It is the single most-deployed estimator on Earth, and it is just that one fuse, run over time.
You are tracking a delivery drone with a single sensor — a GPS-like receiver that, every second, hands you a position. The trouble is the receiver is noisy: each reading is the true position plus a random jitter of a few meters. Plot the raw readings and you get a jagged, trembling scatter that jumps around the true path. If you sent that scatter to the autopilot, the drone would twitch and lurch, chasing every spurious wobble.
But you know something the raw readings don't: the drone has momentum. It cannot teleport. If it was at position 100 going 10 m/s a moment ago, it is almost certainly near 110 now, not back at 50. You have a model of how the drone moves, and that model lets you predict where it should be before the next reading even arrives. The prediction is smooth (it obeys physics) but slowly wrong (the model isn't perfect). The measurement is noisy but anchored to reality.
This is exactly the situation of Lesson 1's GPS-and-IMU pairing and Lesson 3's two-measurement fuse — you have two imperfect sources of the same quantity, and the smart move is to combine them. What's new here is time. Your two sources are no longer two sensors at one instant; they are a prediction (from the model and the last estimate) and a measurement (the fresh reading), and you must fuse them again and again, every tick, forever. The recipe that does this optimally is the Kalman filter, and this whole lesson is about understanding it as exactly that: Lesson 3's inverse-variance fuse, made recursive over time.
Watch the difference below. The drone flies along the dashed truth line. The red dots are the raw, noisy measurements — what one sensor alone gives you. The green line is the Kalman filter's estimate, which fuses each noisy measurement with its own prediction of where the drone should be. Notice how the green line glides through the jitter instead of chasing it.
The drone follows the dashed truth. Red dots are the raw noisy sensor readings — jagged and trembling. The green line is the Kalman estimate, fusing each reading with a physics-based prediction. Press Run. Crank the noise slider and watch the raw dots scatter wildly while the green estimate stays smooth.
The raw dots are honest but unusable — each one trembles by the noise amount. The green estimate is steadier than any single reading, because it doesn't believe any one measurement completely; it blends each with a smooth prediction. That blend is the entire filter. And here is the part that surprises people: the filtered estimate can be more accurate than the true sensor precision, because it pools information across time. A reading at second 5 still slightly informs the estimate at second 6, through the prediction. Information accumulates instead of being thrown away.
A reasonable first instinct: smooth the noise by averaging the last, say, five readings (a "moving average"). It does cut the jitter — but it introduces a fatal flaw: lag. A moving average always reports where the drone was, not where it is, because it's anchored to old data. When the drone accelerates or turns, the average trails behind by exactly the window length. For a fast-moving target that lag is the difference between catching it and losing it.
The Kalman filter has no such lag, because it doesn't blindly average the past — it extrapolates the past forward using the motion model, then corrects with the present reading. It can even estimate velocity it never directly measured, by watching how position changes, and use that velocity to predict ahead. A moving average can never do this; it has no model of motion. The filter is smarter precisely because it carries a model of how the world changes between measurements.
The claim that the filter can beat the raw sensor precision deserves a number, because it's surprising. Suppose every reading is independently noisy with variance R, and the object is stationary (so the prediction is just "it's where it was"). After fusing N independent readings, the inverse-variance rule from Lesson 3 says the precisions add, so the fused variance is R / N. The fused standard deviation — the actual error you feel — shrinks like:
Plug in a sensor with σ = 3 m and watch the estimate sharpen as readings pile up:
| Readings fused (N) | Fused σ = 3 / √N | What it means |
|---|---|---|
| 1 | 3.00 m | one reading — the raw sensor |
| 4 | 1.50 m | twice as sharp as one reading |
| 16 | 0.75 m | four times sharper |
| 100 | 0.30 m | ten times sharper than the sensor itself |
A sensor that's individually wrong by 3 m can, through fusion across 100 readings, pin the position to 30 cm — ten times better than any single reading. That is the magic of accumulating information over time, and it's exactly the precision-adds rule of Lesson 3 run again and again. (The catch: this clean √N law holds only for a stationary target; a moving one is harder, because the prediction injects fresh uncertainty Q each step — which is the whole tension the rest of the lesson resolves.) A moving average can never beat the sensor like this and track motion simultaneously, because to track motion it must keep its window short, which throws away exactly the readings that would have sharpened the estimate.
The recipe was published in 1960 by Rudolf E. Kálmán, an electrical engineer, in a paper that several reviewers nearly rejected because it was so unlike the frequency-domain filtering of the day. Within a decade it was the navigation algorithm aboard the Apollo guidance computer, threading astronauts to the Moon by fusing star-tracker sightings with inertial motion — predict-update, exactly what you'll run by hand in Chapter 5. Today the same five equations run in essentially every phone, car, plane, satellite, drone, and robot that knows where it is, plus in finance, weather prediction, and signal processing. It is, by deployment count, almost certainly the most-used estimation algorithm ever written. And its core — you'll prove this yourself — is the humble weighted average from Lesson 3.
Before the filter can predict or fuse anything, it must hold two things in its head at all times. The first is its best guess of the world; the second is how unsure it is about that guess. Carrying both — not just a number but a number-plus-its-uncertainty — is what makes the Kalman filter a filter and not a mere extrapolator.
The best guess is called the state, written x. The state is the smallest set of numbers that describes everything the filter cares about. For our drone tracking position along a line, a natural state is two numbers — position and velocity:
Why include velocity, which the GPS-like sensor never directly measures? Because velocity is what lets the filter predict. If you know position and velocity, you can say where the drone will be next tick. A state of position alone would have no way to extrapolate. This is a recurring theme: put into the state everything you need to predict the future, even quantities you can't measure. The filter will infer the unmeasured ones (here, velocity) by watching how the measured ones change.
A single number like "position = 100" is a lie of false precision. Are we sure to within a centimeter, or could it be off by 20 meters? The filter answers this with the covariance, written P — a measure of the spread of our error. In the simplest one-variable case (Lesson 3's world), P is just the variance σ²: small P means a tight, confident estimate; large P means a fuzzy, uncertain one.
Picture the estimate not as a point but as a blob. The center of the blob is the state x — our best guess. The size of the blob is the covariance P — our uncertainty. A pin-sharp dot means "I know exactly where the drone is." A wide fuzzy cloud means "it's somewhere in here, but I'm not sure where." The whole filter is the story of this blob moving and breathing: prediction slides and inflates it; a measurement pulls and shrinks it.
With two state variables (position and velocity), the uncertainty is richer than one number. We need to know not just how unsure we are about position and how unsure about velocity, but whether errors in the two are linked. P becomes a small matrix:
Read it like this. The top-left entry σ²pp is the variance of our position error (how fuzzy in p). The bottom-right σ²vv is the variance of our velocity error (how fuzzy in v). The off-diagonal σpv is the covariance — how position and velocity errors move together. If σpv is positive, it means "whenever I've overestimated position, I've probably overestimated velocity too." That coupling is real and useful: a correction to one variable, through the off-diagonal, automatically corrects the other.
For most of this lesson we'll stay in the scalar (one-number) world so the arithmetic is transparent, and bring in the matrix only at the end. But hold the picture: P small = sharp blob, P large = fuzzy blob, off-diagonals = the blob is tilted because the variables are correlated.
That off-diagonal term σpv looks like a technicality, but it's the reason a Kalman filter can estimate a quantity it never directly measures. Here is the chain of logic, and it's worth slowing down for because it surprises everyone the first time.
Our GPS-like sensor measures position only. It says nothing about velocity. Yet the filter happily reports a velocity estimate and uses it to predict. How? Through the correlation. As the filter runs, the predict step creates a correlation between position and velocity errors — because position changes are velocity times time, so an error in position over a step implies something about velocity. Once σpv is non-zero, a measurement that corrects position automatically nudges velocity too, because the math knows the two errors move together. The filter learns velocity by watching how its position corrections trend, exactly as a person learns a car's speed by watching how fast its position changes on a map.
Make the matrix concrete once. Suppose, after a few ticks, the filter holds position with variance 4 m² (σp = 2 m), velocity with variance 1 (m/s)² (σv = 1 m/s), and a positive correlation between the two errors of, say, 1.5 in the off-diagonal. Then:
Read it: we're unsure of position by ±2 m and of velocity by ±1 m/s, and the +1.5 says "when I've overestimated position, I've probably also overestimated velocity." The blob isn't a circle; it's an ellipse tilted diagonally, because the two errors lean the same way. The matrix is always symmetric (the p–v correlation equals the v–p correlation — there's only one relationship between them), which is why P has those mirror-image off-diagonals. Keep this picture; in the showcase you'll watch exactly such an ellipse breathe and tilt as the filter runs.
The green dot is the state estimate (best guess of position). The ring around it is the uncertainty — one standard deviation, set by the covariance P. Drag the slider: small P is a confident pinpoint; large P is a fuzzy "somewhere around here." The dashed line is the (unknown) truth.
Here is the deep reason, and it's the seed of everything that follows. When the filter later fuses a prediction with a measurement, it must decide how much to trust each. That decision is impossible without knowing how uncertain each one is. A measurement from a precise sensor (small variance) deserves more trust than the same number from a sloppy one (large variance). By carrying P, the filter always knows its own confidence, so it can weight the next measurement correctly — exactly the inverse-variance logic of Lesson 3. The covariance is not decoration; it is the steering wheel.
Let's make the pair concrete with starting numbers we'll carry through the rest of the lesson. Say at the outset we believe the drone is at position 100 m moving 10 m/s, but we're quite unsure — position variance 9 m² (so about ±3 m, one standard deviation) and we'll mostly track position:
From this pair, the next chapter will predict forward in time — slide the blob along by the velocity and inflate it because the model isn't perfect — and then fuse a measurement to tighten it again. Keep your eye on these two numbers; they are about to start moving.
Every Kalman tick has two halves. The first half — predict — happens before any new measurement arrives. It answers: "given my current state and my model of how the world moves, where do I expect to be a moment from now, and how sure am I?" It is pure extrapolation, no data needed.
Start with the state. If the drone is at position p moving at velocity v, then after a time step of length Δt it will be at p + v·Δt, still moving at (roughly) v. So the prediction slides the position forward by the velocity. With our starting state x0 = [100, 10] and a one-second step (Δt = 1):
The hat-bar notation p¯ means "predicted, before seeing the measurement." Our predicted state is x¯ = [110, 10]. This is the constant-velocity model: we assume the drone keeps moving at the same speed between ticks. It's a simple model, and like all models it is slightly wrong — real drones accelerate, turn, get pushed by wind. That wrongness is the whole reason the next part matters.
Here is the step beginners forget, and forgetting it is the classic way to break a filter. When you predict forward using an imperfect model, you should become less certain, because the model might be wrong in ways you can't see. So the predict step inflates the covariance. In the scalar case, predicted uncertainty is the old uncertainty plus a chunk for the model's imperfection:
where Q is the process noise — a number that says "how much could reality have deviated from my model during this step?" Q is the filter's humility: it's the admission that the constant-velocity model misses wind, acceleration, and bumps. A small Q means "my model is excellent, trust the prediction." A large Q means "my model is rough, don't trust the prediction too far — stay open to the next measurement."
Let's put numbers on it. We had position variance P0 = 9 m². Say our model imperfection over one second is worth Q = 4 m² (the drone might have drifted by ±2 m beyond what the model predicted). Then:
The blob slid forward to position 110 and inflated from variance 9 to variance 13. We extrapolated, and we paid for it in confidence. This is correct and healthy: prediction always loses information. If we kept predicting with no measurements, P would grow without bound — the blob would balloon — which is the filter's honest way of saying "with no data, I have no idea where you are." (That ballooning is precisely the IMU drift from Lesson 1, encoded as growing covariance.)
To feel the ballooning, run several predict steps in a row with no measurement arriving (a sensor dropout). Position marches forward by 10 each second; variance climbs by Q = 4 each second:
| Tick (no measurement) | Position p¯ | Variance P¯ = P + Q | σ = √P¯ |
|---|---|---|---|
| start | 100 | 9 | 3.0 m |
| after predict 1 | 110 | 9 + 4 = 13 | 3.6 m |
| after predict 2 | 120 | 13 + 4 = 17 | 4.1 m |
| after predict 3 | 130 | 17 + 4 = 21 | 4.6 m |
| after predict 5 | 150 | 29 | 5.4 m |
| after predict 10 | 200 | 49 | 7.0 m |
The variance climbs linearly in the number of steps here (because each step adds a fixed Q), so σ grows like √steps. This is the discrete-time face of the IMU's continuous-time drift from Lesson 1, where you saw position error grow like ½at². In a fuller model where Q itself scales with the timestep, the covariance grows even faster — matching that t² behavior. Either way the message is identical: with no measurements, uncertainty only grows, and the filter knows it. The growing P is precisely what makes the next measurement so welcome — a fuzzy prediction means a high gain, so the filter will lean hard on the data when it finally arrives.
Below, run the predict step repeatedly with no measurements and watch the blob both slide forward and inflate. Turn Q up and the blob balloons fast; turn Q down toward zero and it slides without growing — a recipe for the overconfidence trap.
Each press of Predict advances one time step: the estimate slides forward by its velocity, and its uncertainty ring grows by the process noise Q. With no measurements ever arriving, the blob balloons — the filter honestly losing confidence. Set Q near zero and notice the ring stops growing (the overconfidence trap).
One refinement makes the prediction far better when it applies. Often the system isn't just coasting — you are driving it. A car's throttle, a drone's rotor thrust, a robot's wheel command: these are known control inputs, and a good prediction should use them. If you commanded an acceleration a, the predicted position and velocity become:
In matrix form this is the "+ B·u" term in the predict equation: u is the command you issued (the acceleration), and B maps that command into its effect on the state. The intuition: you don't have to wait for a measurement to know the throttle is open — you commanded it, so fold that knowledge straight into the prediction. A drone that knows it just fired its rotors predicts a better position than one that assumes it's coasting. When there's no command (a thrown ball, a tracked aircraft you don't control), B·u is simply zero and the prediction is pure coasting, exactly our constant-velocity case. The control term costs nothing when absent and sharpens the prediction when present.
The scalar predict step is four lines, and you can read the model directly off them:
python def predict(p, v, P, Q, dt=1.0): p = p + v * dt # slide position forward by velocity P = P + Q # inflate uncertainty by process noise return p, v, P # velocity unchanged (constant-velocity model) # start: position 100, velocity 10, variance 9, model imperfection Q=4 predict(100, 10, 9, 4) # -> (110, 10, 13) slid to 110, variance grew 9 -> 13
Run it twice with no measurement in between and you'd get position 120 with variance 17, then 130 with variance 21 — the position marching out and the uncertainty climbing linearly with Q each step. That growing variance is the filter quietly begging for a measurement. In the next chapter, one arrives.
The predict step left us with a prediction held in some doubt: position 110, variance 13. Now the sensor speaks. A fresh GPS-like reading arrives — call it z. The reading is noisy: it's the true position plus a random error with its own variance R, the measurement noise. A precise sensor has small R; a sloppy one has large R. R is to the measurement what Q was to the model: a statement of how much to doubt it.
Say the reading comes in at z = 115 m, from a sensor whose noise variance is R = 9 m² (about ±3 m). We predicted 110; the sensor says 115. They disagree. The first thing the filter computes is exactly that disagreement — the gap between what we measured and what we predicted we'd measure:
This gap y has a beautiful name: the innovation (sometimes the residual). It is, literally, the new information the measurement carries — the part of the reading we did not already predict. If the innovation were zero, the measurement told us nothing we didn't already know. An innovation of 5 m means "reality is 5 m further along than my model expected" — a genuine surprise the filter must now respond to.
An innovation of 5 m — is that a lot or a little? It depends on how uncertain we were. If we were rock-solid sure of our prediction (tiny P¯) and the sensor is precise (tiny R), then a 5 m gap is shocking — something is wrong. But if both the prediction and the sensor are fuzzy, a 5 m gap is well within the noise, no surprise at all. The filter quantifies "how big could the innovation plausibly be" with the innovation covariance:
Read this carefully, because it's the key to everything: the innovation could plausibly be large for two reasons — either our prediction was uncertain (large P¯) or the sensor is noisy (large R). S adds both doubts together. The expected size of the innovation (one standard deviation) is √S = √22 ≈ 4.7 m. Our actual innovation was 5 m — almost exactly one standard deviation. That's completely normal; no alarm. (If the innovation had been, say, 50 m — more than ten standard deviations — the filter should be deeply suspicious. We'll turn that observation into a fault detector in Chapter 9, the "normalized innovation squared" test.)
python def innovation(z, p_pred, P_pred, R): y = z - p_pred # the surprise: measured minus predicted S = P_pred + R # how big the surprise could plausibly be nis = y * y / S # normalized: (surprise / expected surprise)^2 return y, S, nis innovation(115, 110, 13, 9) # -> y=5, S=22, nis=1.14 surprise is about 1 sigma -> totally normal
That last number, nis (normalized innovation squared), is the innovation measured in units of "expected innovations." A value around 1 means the surprise is exactly the size we'd expect — the filter is well-calibrated and healthy. A value of 100 would mean the surprise is ten standard deviations — either a wild measurement (a glitch) or a filter that has lost the plot. Watching nis is the single best health check for a running filter, and it falls straight out of the innovation we just computed.
Below, drag the prediction and the measurement apart and watch the innovation y open up, while the innovation covariance S (the fuzzy band of "plausible disagreement") tracks how uncertain both sides are. When the gap pokes outside the S band, the filter is genuinely surprised.
The prediction (with its uncertainty P¯) and the measurement z (with its noise R) both claim to know the position. The gap between them is the innovation y. The shaded band is ±1√S of plausible disagreement. Drag the measurement: while it sits inside the band, no surprise; push it outside and the filter should grow suspicious.
Here is a subtle but profound point. The filter never sees the truth — it never gets told "you were 3 meters off." The only feedback it ever receives about its own accuracy is the innovation: the gap between what it predicted and what it measured. Everything the filter knows about whether it's doing well comes from watching this stream of surprises. If the surprises are small and random (sometimes positive, sometimes negative, averaging near zero), the filter is healthy. If they're large, or systematically one-sided (always positive, say), something is wrong — the model is biased, or the sensor is, or a parameter is mistuned.
This is why the innovation is the single most important diagnostic in all of Kalman filtering, and why Chapter 9's debugging section leans on it entirely. A well-tuned filter produces what statisticians call a white innovation sequence: zero-mean, uncorrelated over time, with a spread that matches the filter's own claimed S. When the innovations stop looking white — they drift, or grow, or cluster — the filter is lying to itself, and the innovation is the canary that tells you so. We computed nis ≈ 1.14 above; a long run averaging near 1 is the green light.
We have a prediction (110, uncertainty 13) and a surprising measurement (115, noise 9). We must blend them into one corrected estimate. How much should the measurement move us off the prediction? That single number — the fraction of the surprise we act on — is the Kalman gain, written K. It is the most important quantity in the filter, and it is nothing more than the inverse-variance trust weight you already mastered in Lesson 3.
Let's first recall Lesson 3. There you fused two measurements of the same quantity, a and b, with variances σ²a and σ²b. The optimal fused estimate weighted each by its precision (one over its variance), and the weight on b worked out to:
That last form — "my variance over the sum of variances" — is the key. Now map it onto the filter. The two things being fused are no longer two sensors; they are the prediction (variance P¯) and the measurement (variance R). The Kalman gain is the weight we put on the measurement, and it has the identical form:
Stare at that and the whole filter clicks. K is "the prediction's share of the total uncertainty." If our prediction is very uncertain (P¯ large), most of S comes from the prediction, K is close to 1, and we lean hard on the measurement — the prediction was fuzzy, so let the data correct it. If the sensor is very noisy (R large), most of S comes from R, K is close to 0, and we barely budge off the prediction — the sensor is unreliable, so trust the model. It's the same "trust the more-certain source" rule, now choosing between model and measurement.
We had P¯ = 13 m² (predicted uncertainty) and R = 9 m² (sensor noise). So:
K ≈ 0.59. The filter will move 59% of the way from the prediction toward the measurement. Why 59% and not 50%? Because the prediction (P¯ = 13) was more uncertain than the measurement (R = 9), so the measurement deserves a little more than half the say. The blend tilts toward whichever source is more precise — exactly Lesson 3, made recursive.
The slider sim below makes K's logic visible. Crank up the sensor noise R and watch K slide toward 0 (the filter stops trusting the data and clings to the prediction). Crank up the prediction uncertainty P¯ and watch K climb toward 1 (the filter throws itself at the measurement). The gain is always the prediction's slice of the uncertainty pie.
The pie shows how the total uncertainty S splits between the prediction (P¯) and the measurement (R). The Kalman gain K is the prediction's slice — the fraction of the way the estimate moves toward the measurement. Drag the two sliders and read K. Notice: noisier sensor (big R) → small K (trust the model); fuzzier prediction (big P¯) → big K (trust the data).
The single formula K = P¯/(P¯+R) covers every situation; here are four that span the range, each computed so you can see the logic crystallize:
| Situation | P¯ (prediction) | R (sensor) | K = P¯/(P¯+R) | Filter behavior |
|---|---|---|---|---|
| Balanced (our example) | 13 | 9 | 13/22 = 0.59 | blend, lean slightly to data |
| Trustworthy sensor | 13 | 1 | 13/14 = 0.93 | snap almost to the measurement |
| Noisy sensor | 13 | 100 | 13/113 = 0.12 | cling to the prediction |
| Sensor dropout | 13 | ∞ | 13/∞ = 0 | ignore data, coast on model |
Read the K column top to bottom and the rule is undeniable: K rises when the sensor gets more trustworthy (R small) and falls when the sensor gets less trustworthy (R large), with the dropout case (R→∞) driving K to exactly 0. The filter never needs an if sensor_dead branch — the variance does the switching, exactly as the inverse-variance fuser did in Lesson 1's tunnel. You tune R; the filter computes K.
Why this particular K and not, say, a flat 0.5? Because K = P¯/(P¯+R) is the value that minimizes the variance of the corrected estimate — it produces the sharpest possible blob after the update, just as the inverse-variance weights in Lesson 3 produced the sharpest fused estimate. Any other gain would leave you more uncertain than necessary. We won't grind through the calculus (it's a one-line minimization of the post-update variance with respect to K), but the upshot is the headline word "optimal": for linear-Gaussian problems, no estimator on Earth can do better than this K, every tick. That optimality is inherited directly from Lesson 3's optimality — same proof, now wearing a clock.
Here's the payoff of seeing K as inverse-variance weighting: every intuition you built in Lesson 3 transfers for free. Remember that fusing two estimates produced one sharper than either (the precisions added)? The same thing happens in the filter's update — after applying the gain, the corrected uncertainty is smaller than both P¯ and R. Remember that a sensor going dead (variance → ∞) automatically slid its weight to zero? In the filter, a sensor dropout (R → ∞) drives K → 0, so the filter coasts on its prediction with no special-case code — exactly the GPS-tunnel handoff from Lesson 1, now automatic. The Kalman filter is not a new idea; it is Lesson 3's fuse wearing a clock.
Time to assemble the pieces into one complete tick — predict, then update — with the exact numbers we've carried since Chapter 1. By the end of this chapter you will have run a Kalman filter entirely by hand, and the matrix version will look like the same arithmetic dressed for a crowd of variables.
A scalar Kalman tick is exactly five equations. Two for the predict half, three for the update half:
You've met every one of these already, scattered across the last three chapters. Here they are working together, with our numbers.
Start: position p = 100 m, velocity v = 10 m/s, variance P = 9 m², process noise Q = 4 m², step Δt = 1 s.
The blob slid to 110 and inflated to variance 13. We are now uncertain by √13 ≈ 3.6 m.
The sensor reads z = 115 m, with noise variance R = 9 m². The innovation — the surprise — is:
We'll act on 59.1% of the surprise — the measurement gets slightly more than half the vote because the prediction was the fuzzier of the two.
Move the prediction toward the measurement by K times the innovation:
Read that result like a story. We predicted 110; the sensor shouted 115; we ended at 112.95 — between the two, leaning toward the measurement because K > 0.5. This is the inverse-variance blend of Lesson 3, with the two sources being our own prediction and the new reading. Let's double-check by computing it the Lesson-3 way directly — a weighted average of prediction (110, var 13) and measurement (115, var 9):
Identical, to the rounding. The Kalman update is the two-measurement fuse; "p¯ + K·y" is just an algebraically tidier way to write the same weighted average so it only needs one multiply.
Having fused in a measurement, we are now more confident than we were. The corrected covariance is:
The variance fell from the predicted 13 down to 5.32 — tighter than the prediction's 13 and tighter than the sensor's own 9. Two uncertain witnesses, fused, beat either alone. (Sanity check the Lesson-3 way: 1/P = 1/13 + 1/9 = 0.188, so P = 5.32. Same number.) The blob slid to 112.95 and tightened to √5.32 ≈ 2.31 m. That tightened P now becomes the input to the next predict step — and the cycle repeats forever.
One tick doesn't show the filter; it shows one fuse. The magic is recursion: the output of this tick is the input to the next. Let's run a second tick, starting from where the first ended (p = 112.95, P = 5.32), with the same velocity 10, the same Q = 4 and R = 9, and a new measurement z = 124 m.
Predict:
Notice P¯ this time is 9.32, smaller than last tick's 13 — because we started from a tighter 5.32 instead of 9. The filter is getting more confident as it accumulates information. Innovation: y = 124 − 122.95 = 1.05 m (a small surprise — the model is tracking well now). Gain:
The gain dropped from 0.59 to 0.51 — because the prediction is now nearly as trustworthy as the sensor (P¯ ≈ R), so the filter splits the difference almost evenly. Correct: p = 122.95 + 0.509×1.05 ≈ 123.48 m. Shrink: P = (1 − 0.509)×9.32 ≈ 4.58 m².
Track the covariance across the two ticks: 9 → (predict) 13 → (update) 5.32 → (predict) 9.32 → (update) 4.58. It breathes — inflate, deflate, inflate, deflate — but each update leaves it tighter than the last, until it reaches a steady state where the Q it gains each predict exactly balances the shrinkage each update. At steady state the gain K stops changing too, and the filter settles into a constant trust ratio. (You can solve for that steady-state P and K algebraically — it's the fixed point of the recursion — which is why some embedded filters precompute a fixed gain and skip the covariance bookkeeping entirely. That shortcut is the "steady-state" or "alpha-beta" filter.)
python def kalman_step(p, v, P, z, Q, R, dt=1.0): # --- PREDICT --- p = p + v * dt # slide forward P = P + Q # inflate uncertainty # --- UPDATE --- y = z - p # innovation (surprise) K = P / (P + R) # Kalman gain = trust dial (Lesson 3!) p = p + K * y # nudge toward measurement P = (1 - K) * P # shrink uncertainty return p, v, P # our worked example, one tick: kalman_step(100, 10, 9, 115, 4, 9) # -> (112.95, 10, 5.32) exactly our hand computation
Eight lines. That is a working Kalman filter. Loop it over a stream of measurements and you have the estimator that lands rockets and steers phones. Everything below is about making those same eight lines work when the state has more than one variable.
Our scalar filter tracked position with velocity hidden in a side calculation. Real states have several variables that all evolve together — here, position and velocity as a proper 2-vector x = [p, v]. The five equations become matrix equations, but they say the exact same things. Meet the five matrices:
| Symbol | Name | What it does (scalar analogy) |
|---|---|---|
| F | state-transition matrix | the motion model — how the state evolves one step (the "p + v·Δt" rule, as a matrix) |
| B, u | control matrix & input | how a known command (throttle, steering) moves the state — optional |
| H | measurement matrix | maps the state into what the sensor sees (e.g. "the GPS sees position, not velocity") |
| Q | process-noise covariance | the model imperfection, now a matrix (the scalar Q, generalized) |
| R | measurement-noise covariance | the sensor noise, now a matrix (the scalar R, generalized) |
With F encoding "p ← p + v·Δt, v ← v," the matrix Kalman filter is:
Compare line by line with the scalar version and the correspondence is exact. x¯ = Fx is "slide forward." P¯ = FPFᵀ + Q is "inflate by Q" (the FPFᵀ part rotates the uncertainty through the motion). y = z − Hx¯ is the innovation, with H translating the state into the sensor's units. S = HP¯Hᵀ + R is the innovation covariance — "predicted uncertainty plus sensor noise," same as P¯ + R. K = P¯HᵀS⁻¹ is the gain — "P¯ over S," same as before, just with matrix inverse playing the role of division. And the last two are the identical correct-then-shrink. It is the same five equations.
Let's make the two most important matrices concrete, because seeing them strips away the mystery. Our state is x = [p, v] — position and velocity. The motion model "next position = position + velocity×Δt, velocity unchanged" is encoded by the state-transition matrix F (with Δt = 1):
Read the top row: new position = 1×(old position) + 1×(velocity) = p + v — exactly "slide forward by the velocity." The bottom row: new velocity = 0×p + 1×v = v — "velocity unchanged." The matrix F is literally our Chapter 2 predict rule, written so it handles both variables in one multiply. Now the measurement matrix H. Our GPS sees position only, not velocity, so H picks out the position:
H·x = p says "the sensor's prediction is just the position part of the state." That single zero in H — "the sensor cannot see velocity" — is what makes the filter infer velocity rather than measure it: the innovation only ever corrects position directly, and velocity gets corrected indirectly through the off-diagonal covariance (Chapter 1's secret weapon). With F and H in hand, the five matrix equations are mechanical: F slides the state, H translates it into the sensor's units, and the rest is the same predict-update you ran by hand. Plug F = H = 1 (the scalar case) and they reduce to the eight-line filter above — same equations, fewer variables.
One claim deserves justification: people call the Kalman filter "optimal," and that word is precise, not marketing. If the motion model is linear (F, B are matrices, no curves), and the measurement model is linear (H is a matrix), and all the noise is Gaussian (bell-curve) with the variances Q and R, then the Kalman filter produces the estimate with the smallest possible expected squared error — no other estimator, however clever, can do better. The reason is the same as Lesson 3: for Gaussians, the optimal way to combine two estimates of the same quantity is inverse-variance weighting, and the filter does exactly that at every tick. (When the models are not linear — a turning car, a camera's perspective — the Kalman filter stops being exactly optimal, which is precisely the gap that the Extended Kalman Filter of Lesson 7 fills by linearizing.)
Everything so far has been one variable on a line. Now watch the filter do its real job: track a moving object in two dimensions from noisy measurements, maintaining a position estimate and a covariance ellipse — the 2-D belief blob — in real time. This is the payoff. There's no quiz; the simulation is the test. Break it, and you'll feel every concept from the last five chapters.
An object loops around the canvas (the dashed truth path). Every tick, a noisy measurement of its position arrives — the red dots, scattered by the measurement noise R. The Kalman filter fuses each measurement with its constant-velocity prediction, producing the green estimate and a green covariance ellipse showing its uncertainty. Watch the ellipse breathe: it inflates during prediction and snaps tight when a measurement lands.
The gray dashed path is the truth; red dots are noisy measurements; the green dot + ellipse is the Kalman estimate and its uncertainty. Press Run. Then experiment:
• R (sensor noise): turn it up — measurements scatter wildly, the filter trusts them less (gain drops), the green line stays smooth but lags.
• Q (process noise): turn it up — the filter trusts its model less, the ellipse stays fat, the estimate snaps to every measurement (jumpy). Turn Q toward zero — the ellipse collapses and the filter goes deaf to measurements (divergence).
• Sensor dropout: toggle it — measurements stop, the ellipse balloons as the filter coasts on prediction alone, then snaps back when the sensor returns.
Don't just watch — reproduce the lesson's claims with your own hands:
The tracker runs the same five equations from Chapter 5, but now the state is four numbers — x position, y position, and the two velocities — and the covariance P is a 4×4 matrix. The ellipse you see is the 2-D slice of P over the position variables: its size is the position uncertainty, and (in a full implementation) its tilt shows the correlation between x and y errors. When the object moves diagonally, the ellipse stretches along the direction of motion, because the filter is more certain across the track than along it (it knows the object keeps going, but not exactly how far). That stretching is the off-diagonal covariance from Chapter 1, visible to the naked eye.
The breathing has a precise rhythm tied to the equations. Every predict step the ellipse grows — F slides its center forward and Q inflates it (P¯ = FPFᵀ + Q). Every update step the ellipse shrinks — the measurement pulls the center toward the red dot and (I − KH) tightens it. Run at 20 ticks per second, those two effects blur into a steady gentle pulse. When you drop the sensor, only the grow half runs, so the ellipse balloons monotonically — the pure Lesson-1 drift. Restore the sensor and the shrink half resumes; the ellipse snaps back to its steady-state size within a tick or two, exactly the GPS-returns-after-tunnel behavior you traced numerically in Chapter 7.
This 2-D tracker is, with different sensors plugged into H, the literal core of GPS navigation, radar tracking, and robot localization. Change the motion model (F) and the sensor model (H), and the same five equations track a missile, a stock price, or a robot vacuum. You've now seen the engine that, in one form or another, is running in billions of devices.
Every lesson in this series ends with the same four chapters — Use Cases, Practical Application, Debugging, Connections — because a concept you can't point at in a shipped product is a concept you don't really own. The Kalman filter is, by a wide margin, the most-deployed estimator in engineering history. It flew to the Moon, it's in your pocket, and it sets the price of options. Let's tour where it lives and why it was the right tool each time.
GPS / INS navigation — the canonical complementary fuse. Every phone, car, plane, and missile that knows where it is runs a Kalman filter fusing a GPS receiver (slow, absolute, dies indoors) with an inertial navigation system (fast, relative, drifts). This is Lesson 1's GPS+IMU pairing, made recursive: the predict step propagates the inertial motion (growing P, the drift), and each GPS fix runs the update step (snapping P tight). When you walk into a parking garage and the blue dot keeps moving, that's the predict step coasting; when you step out and it snaps, that's the update. Apollo's guidance computer ran exactly this — the Kalman filter's first flight job.
Radar & air-traffic tracking. A radar sees a noisy blip each sweep. A Kalman filter (with a constant-velocity or constant-acceleration model, exactly our Chapter 5 setup in 2-D) smooths the blips into a track, predicts where the aircraft will be on the next sweep, and gates out clutter. Every blip on an air-traffic controller's screen is a Kalman estimate, not a raw return.
Quantitative finance. The state is a hidden "true" price or volatility; the measurement is the noisy observed price. A Kalman filter estimates the latent state and can flag when the innovation (surprise) spikes — a regime change. Pairs-trading and term-structure models lean on exactly the predict/update loop you ran by hand, with "time" measured in ticks of the market.
Robot localization. A robot vacuum, a warehouse AMR, or a self-driving car fuses wheel odometry, an IMU, and intermittent landmark/GPS fixes through a Kalman filter (often its nonlinear cousins, the EKF/UKF of Lesson 7). The predict step dead-reckons between landmark sightings; each sighting runs the update. The covariance ellipse you watched breathe in the showcase is, in a real robot, the literal uncertainty cloud the planner avoids driving blindly through.
Notice the recurring logic in why engineers reach for the Kalman filter:
| Product | State estimated | Predict model (F) | Measurement (H) |
|---|---|---|---|
| Phone / car navigation | position, velocity | inertial motion (IMU) | GPS fix (position) |
| Air-traffic radar | position, velocity | constant velocity | radar blip (position) |
| Pairs trading | latent fair price | mean-reversion | observed market price |
| Robot vacuum | pose (x, y, heading) | wheel odometry | landmark / wall fix |
| Spacecraft (Apollo → today) | position, velocity, attitude | orbital dynamics | star tracker, ground radar |
Each of these could in principle use a cruder smoother — a moving average, an exponential filter. The Kalman filter wins for three reasons that trace straight back to the chapters you read:
That trio — no lag, correct fusion, self-reported uncertainty — is exactly what a system that must act on its estimate needs, which is why the filter is everywhere a machine has to decide based on noisy sensing.
You can now reverse-engineer a product's estimation design from its marketing:
Tie it all the way back to Lesson 1's tunnel, now with the filter doing the work. A car drives at 20 m/s. The IMU runs the predict step at high rate; GPS supplies the update at 1 Hz with R = 4 m². The process noise (IMU drift per second) is Q = 1 m². Watch the covariance through a GPS dropout and recovery:
| Time | Event | P (uncertainty) | What's happening |
|---|---|---|---|
| t=0 | GPS fix | ~2.0 m² (steady state) | healthy: predict adds 1, update removes ~1 |
| t=1…5 | tunnel: predict only | 3 → 4 → 5 → 6 → 7 | P grows by Q=1 each second, no update to shrink it |
| t=5 | still in tunnel | ~7 m² (σ ≈ 2.6 m) | the car has drifted; the filter knows it's unsure |
| t=6 | GPS returns | snaps to ~1.4 m² | K is high (P¯=8 » R=4), so the fix yanks the estimate back hard |
| t=7+ | fixes resume | settles to ~2.0 m² | back to steady state within a tick or two |
This is Lesson 1's "IMU carries you through the tunnel, GPS resets the drift" — but now quantified. The growing P during the tunnel is the drift made visible; the high gain when GPS returns is why the snap-back is sharp (a fuzzy prediction trusts the data); the settle to steady state is the recursion finding its fixed point. Same story as Lesson 1, told in the language of covariance. And crucially, because the filter tracked its own P through the tunnel, the planner knew not to trust the position too much while underground — a moving average would have given a confident, wrong number with no warning.
Knowing the five equations is not the same as making a real filter behave. The hard part of Kalman filtering in practice is not the math — it's the tuning: picking the process noise Q, the measurement noise R, and the initial covariance P₀, and getting the units and timesteps consistent. This recurring "now build it" chapter is a procedure you can run on any tracking problem.
R is the easiest because it's measurable. It's the variance of your sensor's error. Two ways to get it: read the datasheet (a GPS rated "±3 m, 1σ" means σ = 3 m, so R = 9 m²), or — better — record the sensor while the object is stationary and compute the variance of the readings directly. Always work in variance (σ²), not standard deviation, because that's what the equations use. If your sensor reports two coordinates with independent noise, R is diagonal with each coordinate's variance.
Q is harder because it's not a property of a sensor — it's a statement about how wrong your motion model is. A constant-velocity model on a smoothly cruising plane needs tiny Q (the model is excellent). The same model on a darting hummingbird needs large Q (the model is constantly violated by acceleration). Q is genuinely a tuning knob, and the procedure is:
At t = 0 the filter needs a starting state x₀ and starting uncertainty P₀. The state can be a rough guess (even the first measurement). P₀ should be generous — it encodes how unsure you are at the start. A common mistake is setting P₀ tiny ("I'm sure of my initial guess"), which makes the filter ignore early measurements and converge slowly. Set P₀ large and the filter trusts the first few measurements heavily (gain near 1), snapping quickly to the truth, then P naturally settles to its steady-state value within a handful of ticks. When unsure, start humble: large P₀.
The most common code bug in Kalman filters isn't the algebra — it's inconsistent units and timing. Three rules that prevent the majority of real-world failures:
Task: track a drone's 2-D position from a GPS rated ±2.5 m (1σ), updating at 5 Hz (Δt = 0.2 s). Run the procedure:
Notice every number traces to a question: R to the datasheet, Q to the expected acceleration, P₀ to initial ignorance. None is pulled from the air. That discipline is the difference between a filter that works and one you fight for a week.
Because Q is the main knob, it helps to know what each setting feels like, so you can recognize where you are. Fix R at a sensible value and sweep Q from too-low to too-high; here is the behavior you'll see and the NIS that confirms it:
| Q setting | Gain behavior | Estimate looks… | Mean NIS | Verdict |
|---|---|---|---|---|
| Q = 0 | K → 0 (P collapses) | drifts off, deaf to data | » 1, growing | diverged |
| Q tiny | K small, sluggish | lags on every turn | > 1 | overconfident |
| Q right | K balanced | smooth & responsive | ≈ 1 | healthy |
| Q large | K → 1, jumpy | nearly as noisy as raw data | < 1 | underconfident |
The procedure writes itself from this table: start with Q too small (you'll see lag), and raise it until the lag disappears but before the estimate starts chasing noise — the NIS will pass through ~1 right in that sweet spot. This is exactly what you can reproduce in the Chapter 6 showcase by dragging the Q slider: too low and the ellipse collapses and the estimate drifts; too high and it snaps to every red dot. The tuning is a one-dimensional search with a clear signal (NIS ≈ 1) at the target.
A Kalman filter rarely crashes. It does something worse: it keeps running, producing confident, plausible, wrong estimates while reporting a tight covariance that says "trust me." The failures are quiet, and they all trace to a mismatch between the filter's assumptions and reality. This recurring chapter catalogs the classic traps, the symptom each shows, and the one diagnostic that exposes nearly all of them.
That one diagnostic deserves naming up front: the innovation. A healthy filter's innovations (the surprises y = z − Hx¯) should be small, zero-mean, and consistent with the filter's own claimed uncertainty S. When you normalize them — the NIS, normalized innovation squared, nis = y²/S from Chapter 3 — a healthy filter averages a NIS of about 1. Almost every failure below shows up as a NIS that's persistently too big or too small. (Lesson 21 turns this into a full statistical consistency test; here it's your debugging stethoscope.)
The classic. You set Q too low (or zero), so the predict step barely inflates P. Each measurement shrinks P further; nothing grows it back. Within a few ticks P collapses toward zero, the gain K = P¯/(P¯+R) goes to zero, and the filter stops listening to measurements. It then rides its slightly-wrong model off into the weeds, serenely confident.
Over-correct for Trap 1 and you land in its opposite. With Q huge, the filter never trusts its model, so the gain pins near 1 and the estimate snaps to every noisy measurement — you've rebuilt the raw, jittery scatter you were trying to filter. With R huge, the filter never trusts the measurements and the estimate lags badly. Either way you've thrown away the filter's value.
The two traps above are two ends of one dial. This is the lookup that turns a symptom into an action:
| Symptom | NIS reading | What it means | Fix |
|---|---|---|---|
| Estimate drifts off, covariance tiny | NIS » 1 (and one-sided) | overconfident; ignoring data | raise Q |
| Estimate as noisy as raw data | NIS « 1 | trusts every measurement; Q too high | lower Q |
| Estimate lags behind reality | NIS « 1 | distrusts measurements; R too high | lower R |
| Estimate jumps to glitches | occasional huge NIS spikes | R too low / outliers not gated | raise R, add gating |
Watch the diagnostic actually fire. Two filters track the same object; one has Q set correctly, the other has Q far too small. Here is the NIS = y²/S over the first several ticks of each:
| Tick | Healthy filter NIS | Q-too-small filter NIS | Reading |
|---|---|---|---|
| 1 | 0.8 | 1.1 | both look fine early |
| 2 | 1.2 | 2.4 | the bad one's P is already collapsing |
| 3 | 0.9 | 6.1 | gain shrinking, surprises growing |
| 4 | 1.1 | 18.0 | the bad filter is now ignoring data |
| 5 | 1.0 | 52.0 | runaway — divergence confirmed |
The healthy filter's NIS hovers around 1 forever — surprises are exactly the size it expects. The mistuned one starts fine and then its NIS climbs without bound, because as P collapses the gain dies, the estimate freezes, and every incoming measurement contradicts the frozen estimate more violently than the last. The NIS caught the divergence at tick 3, long before the estimate visibly drifted off the screen. This is the entire value of watching the innovation: it warns you while the fix (raise Q) is still cheap, instead of after the robot has driven into a wall. No amount of staring at the estimate or the covariance alone would have flagged it — the covariance was busy lying, reporting tiny confidence.
P represents a variance — it must stay positive (a covariance matrix must be positive semi-definite). But the simple update P = (1 − K)P¯, run for thousands of ticks in finite-precision arithmetic, can accumulate rounding errors until P develops a negative entry. A "negative variance" is nonsense; the filter produces NaNs or wild gains and detonates. This is rare in the scalar case but a real hazard in matrix filters over long runs.
The filter assumes a specific motion model F and measurement model H. If the real system violates them — a constant-velocity model on a sharply accelerating object, or a linear H on a sensor that's actually nonlinear — the filter is solving the wrong problem. It will still produce smooth, confident estimates that are systematically wrong, especially during the violations (the turns, the accelerations).
One glitched measurement — a GPS multipath reflection, a sensor spike — arrives with a normal R, so the filter trusts it and yanks the estimate toward the garbage. The next few ticks are corrupted. The single bad reading was treated as honest because nothing checked it.
Make the gate concrete. Our filter predicted 110 with P¯ = 13, and the sensor noise is R = 9, so S = 22 and the plausible surprise is √22 ≈ 4.7 m. Now compare a normal reading to a glitch:
| Reading z | Innovation y | NIS = y²/S | Gate (reject if NIS > 9) |
|---|---|---|---|
| 115 (normal) | 5 | 25/22 = 1.1 | accept — fuse it |
| 118 (normal) | 8 | 64/22 = 2.9 | accept |
| 140 (glitch / multipath) | 30 | 900/22 = 40.9 | REJECT — too surprising |
The 140 m reading has an innovation of 30 m — over six standard deviations of surprise. The gate catches it (NIS 40.9 » 9) and the filter skips the update, coasting on its prediction for that one tick rather than letting the garbage yank it. One line of code — if y*y/S > 9: skip the update — converts a fragile filter into one that shrugs off GPS multipath, radar ghosts, and sensor spikes. It is the exact same outlier-rejection logic as Lesson 1's competitive median vote, now guarding the Kalman update from the inside.
| Check | How | Catches |
|---|---|---|
| NIS average | Is mean(y²/S) about 1? | Q/R mistuning (Traps 1 & 2) |
| Innovation whiteness | Are innovations zero-mean and uncorrelated over time? | Wrong model (Trap 4) |
| Covariance positivity | Is every diagonal of P > 0 each tick? | Numerical breakdown (Trap 3) |
| NIS spikes | Any single tick with huge y²/S? | Outliers (Trap 5) |
| Coast test | Drop the sensor — does P grow sensibly and recover on return? | Q too low / predict bugs |
You now hold the most important estimator in engineering, understood from its root: it is Lesson 3's inverse-variance fuse, run recursively over time, fusing a model-based prediction with a measurement at every tick. Before naming what comes next, here is everything in one place — the cheat-sheet to screenshot, the motto, the real sources, and the cross-links.
Screenshot this — the entire filter as one numerical example:
| Step | Equation | Numbers | Result |
|---|---|---|---|
| Predict state | p¯ = p + v·Δt | 100 + 10×1 | 110 m |
| Predict cov | P¯ = P + Q | 9 + 4 | 13 m² |
| Innovation | y = z − p¯ | 115 − 110 | 5 m |
| Gain | K = P¯/(P¯+R) | 13/(13+9) | 0.591 |
| Correct state | p = p¯ + K·y | 110 + 0.591×5 | 112.95 m |
| Shrink cov | P = (1−K)·P¯ | 0.409×13 | 5.32 m² |
This was Lesson 5 of 22 — the first optimal recursive estimator, and the backbone of the classical-estimation arc. It rests directly on Lesson 3 (the one-shot inverse-variance fuse you just saw made recursive) and points straight at the nonlinear world:
| Lessons | Arc | How it builds on the Kalman filter |
|---|---|---|
| 1–3 | Foundations | why fuse; fusion architectures; combining two measurements (the inverse-variance fuse this lesson made recursive) |
| 4–8 | Classical estimation | the Bayes filter (the probabilistic parent); the Kalman filter (here); the EKF & UKF for nonlinear sensors; particle filters for non-Gaussian worlds |
| 9–13 | Spatial fusion | occupancy grids; SLAM; visual-inertial odometry — all built on Kalman-family filters |
| 14–18 | Robust & distributed | fault detection (the innovation/NIS test, formalized); covariance intersection; consensus filters |
| 19–22 | Modern / learned | differentiable filters; learned dynamics; deep fusion — the Kalman structure embedded in neural nets |
Next up, Lesson 6 steps back to the Bayes filter — the general probabilistic framework that the Kalman filter is a special case of (the one where everything is Gaussian and linear). Seeing the Kalman filter as "the Bayes filter, with Gaussians" reveals why it's optimal and exactly where it stops being so — which is the doorway to the EKF and UKF in Lesson 7, where the world finally stops being linear.
These existing lessons go deeper on the machinery we touched: