Streaming Aggregations With a Single Pass (JS)

Welford's online algorithm for mean and variance, plus a 30-line streaming p99 estimator. The version I use when the data does not fit in memory or arrives over WebSocket.

JavaScript
Frontend
3 snippets
stream-processing
performance
code-template
ryancastillo

By @ryancastillo

May 9, 2026

·

Updated May 18, 2026

851 views

26

4.2 (12)

// Welford's online algorithm: incremental mean and variance with no array
// allocation. Numerically stable; the naive sum-of-squares minus square-of-sum
// formula loses precision badly on large samples.

class RunningStats {
    constructor() {
        this.n = 0;
        this.mean = 0;
        this.m2 = 0;
        this.min = Infinity;
        this.max = -Infinity;
    }
    push(x) {
        this.n += 1;
        const delta = x - this.mean;
        this.mean += delta / this.n;
        const delta2 = x - this.mean;
        this.m2 += delta * delta2;
        if (x < this.min) this.min = x;
        if (x > this.max) this.max = x;
    }
    get variance() { return this.n < 2 ? 0 : this.m2 / (this.n - 1); }
    get stddev() { return Math.sqrt(this.variance); }
    summary() {
        return { n: this.n, mean: +this.mean.toFixed(2), stddev: +this.stddev.toFixed(2), min: this.min, max: this.max };
    }
}

const stats = new RunningStats();
for (let i = 0; i < 10_000; i++) {
    const ms = Math.random() < 0.95 ? 30 + Math.random() * 40 : 200 + Math.random() * 300;
    stats.push(ms);
}
console.log(stats.summary());

The traditional formula variance = mean(x^2) - mean(x)^2 is mathematically correct and numerically unstable: when mean(x^2) and mean(x)^2 are close in magnitude, you lose most of your precision to subtractive cancellation. Welford's update sidesteps that by tracking m2, the sum of squared deltas from the running mean, which never has to compute the dangerous difference. Memory is O(1) regardless of sample size, which is what makes it suitable for an unbounded stream. I keep this class in every metrics-collection layer; the summary() method is what gets pushed to the dashboard once a second.