javascript
55 lines · 7 steps
Estimating download speed with a sliding window
A DownloadTracker measures transfer rate over a recent time window to compute live speed and ETA.
Explained by
highlit
1class DownloadTracker {
2 constructor(totalBytes, { windowMs = 3000 } = {}) {
3 this.totalBytes = totalBytes;
4 this.windowMs = windowMs;
5 this.transferred = 0;
6 this.samples = [];
7 }
8
9 update(bytes) {
10 this.transferred += bytes;
11 const now = performance.now();
12 this.samples.push({ t: now, transferred: this.transferred });
13 const cutoff = now - this.windowMs;
14 while (this.samples.length > 1 && this.samples[0].t < cutoff) {
15 this.samples.shift();
16 }
17 }
18
19 get speed() {
20 if (this.samples.length < 2) return 0;
21 const first = this.samples[0];
22 const last = this.samples[this.samples.length - 1];
23 const elapsed = (last.t - first.t) / 1000;
24 if (elapsed <= 0) return 0;
25 return (last.transferred - first.transferred) / elapsed;
26 }
27
28 get eta() {
29 const speed = this.speed;
30 if (speed <= 0) return Infinity;
31 return (this.totalBytes - this.transferred) / speed;
32 }
33
34 formatSpeed() {
35 let value = this.speed;
36 const units = ["B/s", "KB/s", "MB/s", "GB/s"];
37 let i = 0;
38 while (value >= 1024 && i < units.length - 1) {
39 value /= 1024;
40 i++;
41 }
42 return `${value.toFixed(value < 10 && i > 0 ? 1 : 0)} ${units[i]}`;
43 }
44
45 formatEta() {
46 const seconds = this.eta;
47 if (!Number.isFinite(seconds)) return "--:--";
48 const s = Math.round(seconds);
49 const h = Math.floor(s / 3600);
50 const m = Math.floor((s % 3600) / 60);
51 const sec = s % 60;
52 const pad = (n) => String(n).padStart(2, "0");
53 return h > 0 ? `${h}:${pad(m)}:${pad(sec)}` : `${pad(m)}:${pad(sec)}`;
54 }
55}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Measuring rate over a recent time window smooths out bursts and stale readings better than a lifetime average.
- 2Getters let derived values like speed and ETA stay always-current without manual recomputation.
- 3Trimming old samples keeps memory bounded while preserving enough data to compute a meaningful slope.
Related explainers
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
javascript
const SWIPE_THRESHOLD = 80; const MAX_TRANSLATE = 120; export function attachSwipeToDismiss(element, onDismiss) {
Building a swipe-to-dismiss gesture in JS
touch-events
gesture-detection
dom-manipulation
Intermediate
10 steps
javascript
const { pool } = require('./db'); function withTransaction() { return async (req, res, next) => {
A per-request transaction middleware in Express
middleware
database-transactions
connection-pooling
Advanced
7 steps
javascript
function collapseConsecutiveLogs(lines, { keyFn = (l) => l.message } = {}) { const groups = []; for (const line of lines) {
Collapsing consecutive log lines in JavaScript
grouping
run-length-encoding
data-transformation
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/estimating-download-speed-with-a-sliding-window-explained-javascript-7d18/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.