Digital Filters: FIR, IIR & Smoothing Lag
The two filter families and what separates them — finite convolution with exactly linear phase versus feedback with poles you must keep inside the unit circle — and the result every metrics dashboard runs into: an exponential moving average is a one-pole IIR filter whose lag is exactly (1-α)/α samples, so more smoothing always buys more delay.
A digital filter is one difference equation: each output sample is a weighted sum of recent inputs, and possibly of recent outputs. Whether that second term exists is the entire taxonomy. Without feedback the impulse response is finite (FIR), the filter is unconditionally stable, and symmetry buys you exactly linear phase. With feedback the impulse response rings on forever (IIR), you get a steep response from three coefficients instead of three hundred, and you inherit a stability condition and a phase distortion you cannot remove causally.
The practical payoff sits at the end of that chain: the exponential moving average that smooths your latency graph is a one-pole IIR low-pass filter, and its lag is a closed-form function of its smoothing constant. You do not get to tune "smoothness" and "responsiveness" independently — they are the same knob read from two ends.
The recursion equation #
The are feedforward coefficients, the feedback. There is no : that would be the sample being computed. Smith calls this the recursion equation and the coefficients the recursion coefficients.
- → FIR. The output is a plain convolution of the input with the coefficient vector, which is the impulse response: . Feed it a delta and the output stops after samples.
- → IIR. Feed it a delta and the output is a sum of decaying exponentials (and, with complex pole pairs, decaying sinusoids) that never reaches exactly zero.
Taking the z-transform turns the recursion into a rational function:
An FIR filter has a constant denominator — all its poles sit at the origin, so it cannot be unstable. An IIR filter has genuine poles, and stability requires every pole strictly inside the unit circle ; see filter stability. Evaluate on the unit circle, , and you have the frequency response — the DFT of the impulse response.
flowchart LR
x["x[n]"] --> d1["z-1"]
d1 --> d2["z-1"]
d2 --> d3["z-1"]
x --> S(("+"))
d1 --> S
d2 --> S
d3 --> S
S --> y["y[n]"]
Three delays, four taps, no path from the output back into the sum: the impulse response ends after four samples. The IIR of the same order carries one feedback tap instead, and that tap is what makes the response infinite:
flowchart LR
xi["x[n]"] --> ga["x alpha"]
ga --> Si(("+"))
Si --> yi["y[n]"]
yi --> di["z-1"]
di --> gb["x (1-alpha)"]
gb --> Si
Linear phase, and why only FIR gets it #
If a length- FIR impulse response is symmetric () or antisymmetric, its frequency response factors into a real amplitude times a pure delay term . Both phase delay and group delay are then exactly samples at every frequency — one constant delay, no frequency-dependent smearing. Every component of a pulse arrives together, so the shape of a transient survives the filter; only its arrival time moves.
A causal recursive filter cannot do this, because a symmetric impulse response would have to start before . Every causal IIR filter has frequency-dependent group delay, which is why an IIR-smoothed step overshoots or lags unevenly across its spectrum while an FIR-smoothed one just arrives late.
FIR versus IIR #
| FIR | IIR | |
|---|---|---|
| Stability | unconditional — all poles at the origin | requires every pole inside the unit circle; quantized coefficients can push one out |
| Phase | exactly linear if symmetric; constant group delay (N−1)/2 | nonlinear by construction; group delay peaks near band edges |
| Coefficients for a steep response | tens to hundreds of taps | a handful — a 9th-order elliptic beats a 362-tap FIR on the same spec |
| State per channel | N−1 delay elements | order of the filter, typically 2–8 words |
| Arithmetic sensitivity | benign — errors do not recirculate | limit cycles, overflow oscillation, coefficient quantization moves poles |
| Design | windowing or Parks–McClellan; response is directly the kernel's DFT | analog prototype + bilinear transform, or iterative fitting |
| Adaptive / streaming use | cheap to change coefficients, expensive to run | O(1) per sample, tiny state — the reason every metrics system uses one |
A rough FIR sizing rule (Kaiser's, as used by kaiserord): for stopband attenuation dB and transition width radians/sample,
Take a concrete spec: passband edge 0.1 cycles/sample, stopband edge 0.11 (a transition width of rad/sample), 60 dB stopband attenuation.
| Design | Order needed |
|---|---|
| Kaiser-window FIR | ~362 taps (181 samples of delay) |
| Butterworth IIR | 86 |
| Chebyshev type I IIR (0.1 dB passband ripple) | 21 |
| Elliptic IIR (0.1 dB passband ripple) | 9 — five biquads |
Orders from the standard degree formulas, with bilinear prewarping. Two orders of magnitude in arithmetic between the ends of that table is why IIR survives despite its sharp edges — and the ordering of the four rows is the classic tradeoff: flat passband, then ripple in one band, then ripple in both, each step buying steepness.
The moving average: optimal in time, catastrophic in frequency #
This is an FIR filter whose kernel is a rectangular pulse of area one. Two facts about it are worth internalizing, and they point in opposite directions.
In the time domain it is optimal. Of all linear filters, the boxcar produces the lowest output noise for a given step-response risetime, because when the noise is white no input sample deserves preferential weighting. Noise is reduced by : a 100-point average cuts white noise by 10×.
In the frequency domain it is the worst filter in common use. Its magnitude response is the Dirichlet kernel (the DFT of a rectangle):
with in cycles/sample over . Two properties ruin it as a low-pass filter:
| M | Group delay | −3 dB cutoff | First null | Worst stopband lobe | Noise reduction |
|---|---|---|---|---|---|
| 3 | 1.0 | 0.1553 | 0.3333 | −9.5 dB | 1.73× |
| 5 | 2.0 | 0.0902 | 0.2000 | −12.0 dB | 2.24× |
| 11 | 5.0 | 0.0404 | 0.0909 | −13.0 dB | 3.32× |
| 31 | 15.0 | 0.0143 | 0.0323 | −13.2 dB | 5.57× |
| 101 | 50.0 | 0.0044 | 0.0099 | −13.3 dB | 10.05× |
Frequencies in cycles/sample; delay and cutoff computed from the equations above.
- The stopband floor does not improve with . The first sidelobe converges to −13.3 dB and stays there no matter how long the window. Lengthening the average narrows the passband but never buys rejection: an interferer parked on the first sidelobe is attenuated by a factor of 4.6, forever. The sidelobe envelope then falls at only −6 dB/octave.
- The −3 dB cutoff is pinned to the length: . You cannot choose cutoff and delay separately — delay is , so cutoff and delay are locked in inverse proportion.
The redeeming implementation detail: a running sum makes it per sample regardless of , using one add and one subtract.
(Smith writes the centred variant, with , — same filter, shifted forward by samples, which is only available offline.)
The EMA is a one-pole IIR filter #
Every metrics stack ships this line:
smoothed = alpha * sample + (1 - alpha) * smoothedWritten as a difference equation with :
Take the z-transform: , so
That is one pole at and one zero at the origin — the smallest nontrivial IIR filter there is. Everything follows from that pole.
Impulse response. for : a geometric decay summing to 1, so the filter has unity DC gain. Infinite support, hence IIR.
Stability. The pole is inside the unit circle iff , i.e. . In practice ; at the filter is the identity. This is exactly Smith's single-pole design, , with the per-sample decay.
Time constant. , where is the number of samples to decay to . So
Cutoff. Setting and solving exactly:
Smith's design equation is the RC-analogy approximation of the same thing; for the two agree to four decimals (0.01678 vs 0.01677), and they only visibly diverge above (at : 0.1150 vs 0.1103, ~4%).
Lag. The phase is , and differentiating gives the group delay in closed form:
This also explains the folklore constant used to make an "N-period EMA" comparable to an N-point simple average. Substituting gives — exactly the group delay of the length- boxcar. The convention is a lag-matching rule, not an approximation of the kernel.
| α | "N-period" 2/(N+1) | Lag (samples) | τ, samples to 36.8% | fc (cyc/sample) | Period at fc | Samples to 95% of a step |
|---|---|---|---|---|---|---|
| 0.5 | 3 | 1.0 | 1.44 | 0.1150 | 8.7 | 4.3 |
| 0.25 | 7 | 3.0 | 3.48 | 0.0461 | 21.7 | 10.4 |
| 0.125 | 15 | 7.0 | 7.49 | 0.0213 | 47.0 | 22.4 |
| 0.1 | 19 | 9.0 | 9.49 | 0.0168 | 59.6 | 28.4 |
| 0.05 | 39 | 19.0 | 19.50 | 0.0082 | 122.5 | 58.4 |
| 0.02 | 99 | 49.0 | 49.50 | 0.0032 | 311.0 | 148.3 |
| 0.01 | 199 | 99.0 | 99.50 | 0.0016 | 625.2 | 298.1 |
Read the last two columns together with an alert threshold. With on a 15-second scrape interval, a step change in the underlying signal takes ~298 samples ≈ 75 minutes to be 95% reflected in the smoothed series, and the curve's midpoint is offset by 99 samples ≈ 25 minutes. If you page on that series, that is your floor on detection latency, independent of the alerting system.
EMA versus boxcar at equal lag #
Fix the lag and compare honestly — a causal boxcar of length against an EMA with , both with group delay samples:
| Lag L | M | EMA α | fc boxcar | fc EMA | Worst stopband, boxcar | EMA at f = 0.25 |
|---|---|---|---|---|---|---|
| 3 | 7 | 0.250 | 0.0638 | 0.0461 | −12.7 dB | −14.0 dB |
| 5 | 11 | 0.167 | 0.0404 | 0.0291 | −13.0 dB | −17.9 dB |
| 15 | 31 | 0.0625 | 0.0143 | 0.0103 | −13.2 dB | −26.8 dB |
| 50 | 101 | 0.0196 | 0.0044 | 0.0032 | −13.3 dB | −37.1 dB |
The EMA's response is monotonic — it never comes back up — so its attenuation keeps improving with frequency, while the boxcar's worst case is stuck at −13 dB no matter how long you make it. At the equal-lag pairing the two have identical gain at Nyquist, . Where the boxcar wins is the sharp step response and the exact nulls; where the EMA wins is monotonic rejection, state, and no buffer.
Where this already runs in your stack #
Linux load average. Three one-pole EMAs over the runnable-task count, sampled every LOAD_FREQ = 5*HZ+1 ≈ 5 s. The decay constants live in include/linux/sched/loadavg.h as fixed point with FSHIFT = 11:
which are exactly , , . The names "1, 5, 15 minute" are time constants, not window lengths — the 1-minute figure is 63% converged after 60 s, not 100%, which is why load average reacts visibly slower than people expect it to.
TCP's retransmission timer. RFC 6298 smooths the RTT estimate with two EMAs:
with and . puts SRTT 7 samples behind the true RTT — which is precisely why the timeout is with , rather than trusting a lagging mean on its own.
Adam. with the default is an EMA with → 9 steps of lag; with is → 999 steps of lag. The paper's bias correction is exactly the warm-up fix described below.
Sharp edges #
Offline? Then you can have zero phase
filtfilt runs the filter forward, reverses the sequence, and runs it again. The combined response is : real and non-negative, so zero phase and zero lag, at the cost of squaring the magnitude response (the effective −3 dB point moves, and stopband attenuation doubles in dB) and doubling the effective order. Edge transients need explicit handling.
This is strictly non-causal — it needs the whole series — so it is for post-hoc analysis, backfilled dashboards, and offline signal cleanup. It is not available to anything that has to make a decision now, which is exactly the situation in which people wish lag away.
Choosing one #
| Situation | Use |
|---|---|
| Streaming metric, want O(1) state and a tunable time constant | One-pole EMA with α from a time constant, not a magic number |
| Need the smoothed series to preserve transient shape | Linear-phase FIR; accept delay explicitly |
| Killing a known periodic interferer (mains, a fixed-rate job) | Boxcar of exactly one interferer period, or a notch biquad |
| Genuine band separation (separating signals by frequency) | Designed filter — Butterworth for flat passband, Chebyshev for steeper roll-off at the cost of ripple, elliptic for the steepest at the cost of ripple in both bands |
| Spiky outliers rather than broadband noise | Median filter first — no linear filter removes an impulse, it just spreads it over the kernel |
| Offline analysis, lag is unacceptable | filtfilt / forward-backward, zero phase |
Sources
- bookThe Scientist and Engineer's Guide to Digital Signal Processing, Ch. 15 — Moving Average Filters (Smith)
- bookThe Scientist and Engineer's Guide to Digital Signal Processing, Ch. 19 — Recursive Filters (Smith)
- bookLinear-Phase Filters (Symmetric Impulse Responses) — Julius O. Smith III, CCRMA
- bookUnder the Hood of kaiserord — Spectral Audio Signal Processing, CCRMA
- docsRFC 6298 — Computing TCP's Retransmission Timer
- codeLinux kernel — include/linux/sched/loadavg.h
- paperAdam: A Method for Stochastic Optimization (Kingma & Ba, ICLR 2015)
- docsscipy.signal.filtfilt — zero-phase forward-backward filtering