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.

builds onSampling, Nyquist & AliasingConvolution & Correlation

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 #

y[n]=∑k=0Pak x[n−k]  +  ∑k=1Qbk y[n−k]y[n] = \sum_{k=0}^{P} a_k\, x[n-k] \; + \; \sum_{k=1}^{Q} b_k\, y[n-k]

The aka_k are feedforward coefficients, the bkb_k feedback. There is no b0b_0: that would be the sample being computed. Smith calls this the recursion equation and the coefficients the recursion coefficients.

  • Q=0Q = 0 → FIR. The output is a plain convolution of the input with the coefficient vector, which is the impulse response: h[n]=anh[n] = a_n. Feed it a delta and the output stops after P+1P+1 samples.
  • Q>0Q > 0 → 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:

H(z)=∑k=0Pakz−k1−∑k=1Qbkz−kH(z) = \frac{\sum_{k=0}^{P} a_k z^{-k}}{1 - \sum_{k=1}^{Q} b_k z^{-k}}

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 ∣z∣<1|z| < 1; see filter stability. Evaluate H(z)H(z) on the unit circle, z=ejωz = e^{j\omega}, and you have the frequency response — the DFT of the impulse response.

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:

Linear phase, and why only FIR gets it #

If a length-NN FIR impulse response is symmetric (h[n]=h[N−1−n]h[n] = h[N-1-n]) or antisymmetric, its frequency response factors into a real amplitude times a pure delay term e−jω(N−1)/2e^{-j\omega(N-1)/2}. Both phase delay and group delay are then exactly (N−1)/2(N-1)/2 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 n=0n = 0. 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 #

FIRIIR
Stabilityunconditional — all poles at the originrequires every pole inside the unit circle; quantized coefficients can push one out
Phaseexactly linear if symmetric; constant group delay (N−1)/2nonlinear by construction; group delay peaks near band edges
Coefficients for a steep responsetens to hundreds of tapsa handful — a 9th-order elliptic beats a 362-tap FIR on the same spec
State per channelN−1 delay elementsorder of the filter, typically 2–8 words
Arithmetic sensitivitybenign — errors do not recirculatelimit cycles, overflow oscillation, coefficient quantization moves poles
Designwindowing or Parks–McClellan; response is directly the kernel's DFTanalog prototype + bilinear transform, or iterative fitting
Adaptive / streaming usecheap to change coefficients, expensive to runO(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 AA dB and transition width Δω\Delta\omega radians/sample,

M≈A−82.285 ΔωM \approx \frac{A - 8}{2.285\,\Delta\omega}

Take a concrete spec: passband edge 0.1 cycles/sample, stopband edge 0.11 (a transition width of 0.02π0.02\pi 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 #

y[i]=1M∑j=0M−1x[i−j]y[i] = \frac{1}{M}\sum_{j=0}^{M-1} x[i-j]

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 M\sqrt{M}: 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):

∣H(f)∣=∣sin⁡(πfM)Msin⁡(πf)∣,H(0)=1|H(f)| = \left| \frac{\sin(\pi f M)}{M \sin(\pi f)} \right|, \qquad H(0) = 1

with ff in cycles/sample over [0,0.5][0, 0.5]. Two properties ruin it as a low-pass filter:

MGroup delay−3 dB cutoffFirst nullWorst stopband lobeNoise reduction
31.00.15530.3333−9.5 dB1.73×
52.00.09020.2000−12.0 dB2.24×
115.00.04040.0909−13.0 dB3.32×
3115.00.01430.0323−13.2 dB5.57×
10150.00.00440.0099−13.3 dB10.05×

Frequencies in cycles/sample; delay and cutoff computed from the equations above.

  1. The stopband floor does not improve with MM. 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.
  2. The −3 dB cutoff is pinned to the length: fc≈0.443/Mf_c \approx 0.443/M. You cannot choose cutoff and delay separately — delay is (M−1)/2(M-1)/2, so cutoff and delay are locked in inverse proportion.

The redeeming implementation detail: a running sum makes it O(1)O(1) per sample regardless of MM, using one add and one subtract.

y[i]=y[i−1]+x[i]−x[i−M]My[i] = y[i-1] + \frac{x[i] - x[i-M]}{M}

(Smith writes the centred variant, x[i+p]−x[i−q]x[i+p] - x[i-q] with p=M−12p = \tfrac{M-1}{2}, q=p+1q = p+1 — same filter, shifted forward by pp samples, which is only available offline.)

The EMA is a one-pole IIR filter #

Every metrics stack ships this line:

python
smoothed = alpha * sample + (1 - alpha) * smoothed

Written as a difference equation with β=1−α\beta = 1 - \alpha:

y[n]=α x[n]+β y[n−1]y[n] = \alpha\,x[n] + \beta\,y[n-1]

Take the z-transform: Y(z)=αX(z)+βz−1Y(z)Y(z) = \alpha X(z) + \beta z^{-1} Y(z), so

  H(z)=α1−(1−α) z−1  \boxed{\;H(z) = \frac{\alpha}{1 - (1-\alpha)\,z^{-1}}\;}

That is one pole at z=1−αz = 1-\alpha and one zero at the origin — the smallest nontrivial IIR filter there is. Everything follows from that pole.

Impulse response. h[n]=α(1−α)nh[n] = \alpha(1-\alpha)^n for n≥0n \ge 0: 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 ∣1−α∣<1|1-\alpha| < 1, i.e. 0<α<20 < \alpha < 2. In practice α∈(0,1]\alpha \in (0,1]; at α=1\alpha = 1 the filter is the identity. This is exactly Smith's single-pole design, a0=1−x,  b1=xa_0 = 1-x,\; b_1 = x, with x=1−αx = 1-\alpha the per-sample decay.

Time constant. x=e−1/dx = e^{-1/d}, where dd is the number of samples to decay to 1/e≈36.8%1/e \approx 36.8\%. So

d=−1ln⁡(1−α)d = \frac{-1}{\ln(1-\alpha)}

Cutoff. Setting ∣H∣2=12|H|^2 = \tfrac12 and solving exactly:

cos⁡ωc=1−α22(1−α),fc=ωc2π\cos \omega_c = 1 - \frac{\alpha^2}{2(1-\alpha)}, \qquad f_c = \frac{\omega_c}{2\pi}

Smith's design equation x=e−2πfcx = e^{-2\pi f_c} is the RC-analogy approximation of the same thing; for α=0.1\alpha = 0.1 the two agree to four decimals (0.01678 vs 0.01677), and they only visibly diverge above α≈0.4\alpha \approx 0.4 (at α=0.5\alpha = 0.5: 0.1150 vs 0.1103, ~4%).

Lag. The phase is φ(ω)=−arg⁡ ⁣(1−βe−jω)\varphi(\omega) = -\arg\!\left(1 - \beta e^{-j\omega}\right), and differentiating gives the group delay in closed form:

τg(ω)=βcos⁡ω−β21−2βcos⁡ω+β2⟹τg(0)=β1−β=  1−αα  \tau_g(\omega) = \frac{\beta\cos\omega - \beta^{2}}{1 - 2\beta\cos\omega + \beta^{2}} \qquad\Longrightarrow\qquad \tau_g(0) = \frac{\beta}{1-\beta} = \boxed{\;\frac{1-\alpha}{\alpha}\;}

This also explains the folklore constant α=2/(N+1)\alpha = 2/(N+1) used to make an "N-period EMA" comparable to an N-point simple average. Substituting gives τg(0)=N−12\tau_g(0) = \frac{N-1}{2} — exactly the group delay of the length-NN 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 fcSamples to 95% of a step
0.531.01.440.11508.74.3
0.2573.03.480.046121.710.4
0.125157.07.490.021347.022.4
0.1199.09.490.016859.628.4
0.053919.019.500.0082122.558.4
0.029949.049.500.0032311.0148.3
0.0119999.099.500.0016625.2298.1

Read the last two columns together with an alert threshold. With α=0.01\alpha = 0.01 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 M=2L+1M = 2L+1 against an EMA with α=1/(L+1)\alpha = 1/(L+1), both with group delay LL 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, 1/M1/M. Where the boxcar wins is the sharp step response and the exact nulls; where the EMA wins is monotonic rejection, O(1)O(1) 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:

EXP_1=1884,EXP_5=2014,EXP_15=2037out of 2048\texttt{EXP\_1} = 1884,\quad \texttt{EXP\_5} = 2014,\quad \texttt{EXP\_15} = 2037 \quad \text{out of } 2048

which are exactly 2048 e−5/60=1884.252048\,e^{-5/60} = 1884.25, 2048 e−5/300=2014.152048\,e^{-5/300} = 2014.15, 2048 e−5/900=2036.652048\,e^{-5/900} = 2036.65. 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:

SRTT←(1−α) SRTT+αR′,RTTVAR←(1−β) RTTVAR+β ∣SRTT−R′∣\text{SRTT} \leftarrow (1-\alpha)\,\text{SRTT} + \alpha R', \qquad \text{RTTVAR} \leftarrow (1-\beta)\,\text{RTTVAR} + \beta\,|\text{SRTT} - R'|

with α=1/8\alpha = 1/8 and β=1/4\beta = 1/4. α=1/8\alpha = 1/8 puts SRTT 7 samples behind the true RTT — which is precisely why the timeout is RTO=SRTT+max⁡(G,K⋅RTTVAR)\text{RTO} = \text{SRTT} + \max(G, K \cdot \text{RTTVAR}) with K=4K = 4, rather than trusting a lagging mean on its own.

Adam. mt=β1mt−1+(1−β1) gtm_t = \beta_1 m_{t-1} + (1-\beta_1)\,g_t with the default β1=0.9\beta_1 = 0.9 is an EMA with α=0.1\alpha = 0.1 → 9 steps of lag; vtv_t with β2=0.999\beta_2 = 0.999 is α=0.001\alpha = 0.001 → 999 steps of lag. The paper's bias correction m^t=mt/(1−β1 t)\hat m_t = m_t / (1 - \beta_1^{\,t}) 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 H(ejω)H(ejω)‾=∣H(ejω)∣2H(e^{j\omega})\overline{H(e^{j\omega})} = |H(e^{j\omega})|^2: 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 (N−1)/2(N-1)/2 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

  1. bookThe Scientist and Engineer's Guide to Digital Signal Processing, Ch. 15 — Moving Average Filters (Smith)
  2. bookThe Scientist and Engineer's Guide to Digital Signal Processing, Ch. 19 — Recursive Filters (Smith)
  3. bookLinear-Phase Filters (Symmetric Impulse Responses) — Julius O. Smith III, CCRMA
  4. bookUnder the Hood of kaiserord — Spectral Audio Signal Processing, CCRMA
  5. docsRFC 6298 — Computing TCP's Retransmission Timer
  6. codeLinux kernel — include/linux/sched/loadavg.h
  7. paperAdam: A Method for Stochastic Optimization (Kingma & Ba, ICLR 2015)
  8. docsscipy.signal.filtfilt — zero-phase forward-backward filtering

Searches titles, aliases, headings, summaries, and full text.↑↓ move ↵ open esc close