Performance — Debounce, Throttle, Memoize
3G নেটওয়ার্ক ও সস্তা ফোনেও smooth app
1. Why Bangladesh Cares Extra
Most Bangladeshi users browse on entry-level Androids over 3G. A single unoptimised search box that fires a fetch on every keystroke can saturate the connection. The cost of a lazy frontend is felt the most on the slowest devices.
2. Debounce — wait for silence
Use case: search-as-you-type. Run the function only after the user pauses for X ms.
function debounce(fn, ms) {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), ms);
};
}
const search = debounce(q => console.log("search:", q), 200);
search("j"); search("ja"); search("jav"); search("java");
// Only "java" runs, ~200 ms after the last call.
3. Throttle — at most every X ms
Use case: scroll handler, mouse move, resize. Fire at most once per period.
function throttle(fn, ms) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= ms) {
last = now;
fn(...args);
}
};
}
const log = throttle(n => console.log("call", n), 50);
for (let i = 0; i < 10; i++) log(i);
// Only the first call runs (loop is sync, all in same ms window).
| Pattern | Fires | Use For |
|---|---|---|
| Debounce | After silence | Search box, autosave |
| Throttle | At most every X ms | Scroll, drag, resize |
4. requestAnimationFrame
For smooth animations and visual updates, use rAF instead of setInterval. The callback runs once per repaint (~16.7 ms at 60 fps), automatically pauses when the tab is hidden.
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
5. Memoize
function memoize(fn) {
const cache = new Map();
return x => {
if (!cache.has(x)) cache.set(x, fn(x));
return cache.get(x);
};
}
const slowFib = n => n < 2 ? n : slowFib(n - 1) + slowFib(n - 2);
const fastFib = (() => {
const cache = new Map();
const rec = n => {
if (cache.has(n)) return cache.get(n);
const r = n < 2 ? n : rec(n - 1) + rec(n - 2);
cache.set(n, r);
return r;
};
return rec;
})();
console.log(fastFib(40)); // instant
6. Lazy Loading
<!-- Image: only load when in viewport -->
<img src="logo.png" loading="lazy" alt="">
// Code-split: load module only when needed
const onClick = async () => {
const { drawChart } = await import("./chart.js");
drawChart(data);
};
7. Performance API
const t0 = performance.now();
let sum = 0;
for (let i = 0; i < 1e6; i++) sum += i;
const t1 = performance.now();
console.log("sum took", (t1 - t0).toFixed(2), "ms");
// Mark + measure (for DevTools timeline)
performance.mark("a");
for (let i = 0; i < 5e5; i++);
performance.mark("b");
performance.measure("work", "a", "b");
console.log(performance.getEntriesByName("work")[0].duration);
8. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Debounce | Run only after a quiet pause — best for search-as-you-type. | চুপ হলে চলে — search-এ ভালো। |
| Throttle | Run at most every N ms — best for scroll/drag/resize. | প্রতি N ms-এ একবার — scroll-এ ভালো। |
| Memoize | Cache function results by their input arguments. | Input অনুযায়ী result cache। |
requestAnimationFrame | Schedules a callback before the next repaint (~60 fps). | পরের paint-এর আগে চলে — ~60 fps। |
| Lazy load | Fetch resources only when needed (image / module). | প্রয়োজনে load করা। |
| Code splitting | Split the bundle so users download only what they use. | Bundle ভাগ করে যা দরকার সেটাই পাঠানো। |
performance.now | High-resolution timestamp for measurements. | High-res timestamp — measurement-এর জন্য। |
| Long task | Sync work over ~50 ms — visibly stutters the UI. | ~50 ms-এর বেশি sync কাজ — UI freeze। |
| Critical path | The first set of bytes needed before the page is usable. | প্রথমে usable হতে যা byte লাগে। |
loading="lazy" img-এ যোগ করা সবচেয়ে সহজ ১-line optimization।
9. Practice Problems
- Build debounce and demonstrate with rapid calls.
✨ Show Answer
a1.jsconst debounce = (fn, ms) => { let id; return (...a) => { clearTimeout(id); id = setTimeout(() => fn(...a), ms); }; }; const log = debounce(s => console.log("final:", s), 100); log("a"); log("ab"); log("abc"); - Build throttle.
✨ Show Answer
a2.jsconst throttle = (fn, ms) => { let last = 0; return (...a) => { const n = Date.now(); if (n - last >= ms) { last = n; fn(...a); } }; }; const hit = throttle(i => console.log("hit", i), 50); for (let i = 0; i < 5; i++) hit(i); - Memoize a slow square function and prove the cache works.
✨ Show Answer
a3.jsconst memoize = fn => { const c = new Map(); return x => c.has(x) ? c.get(x) : (c.set(x, fn(x)), c.get(x)); }; let calls = 0; const sq = memoize(n => { calls++; return n * n; }); sq(3); sq(3); sq(3); sq(4); console.log("actual calls:", calls); - When to use debounce vs throttle?
✨ Show Answer
Answer: Debounce when you only care about the final value (search, autosave) — wait for silence. Throttle when you want a steady stream but rate-limited (scroll, drag, mouse move) — fire at most every X ms.
- Time how long sorting 100k random numbers takes.
✨ Show Answer
a5.jsconst arr = Array.from({ length: 100_000 }, () => Math.random()); const t0 = performance.now(); arr.sort((a, b) => a - b); console.log((performance.now() - t0).toFixed(1), "ms"); - Sketch a search-as-you-type input that uses debounce.
✨ Show Answer
const search = debounce(async q => { const r = await fetch(`/api/search?q=${encodeURIComponent(q)}`); show(await r.json()); }, 300); input.addEventListener("input", e => search(e.target.value)); - Why does
requestAnimationFramebeatsetInterval(..., 16)?✨ Show Answer
Answer: rAF aligns with the browser's actual paint cycle, runs at the device's true refresh rate (which can be 60/90/120 Hz), and pauses while the tab is in the background — saving CPU/battery.
setIntervalkeeps firing regardless and drifts off the frame boundary. - Memoize a multi-arg function with a stringified key.
✨ Show Answer
a8.jsconst memo = fn => { const c = new Map(); return (...a) => { const k = JSON.stringify(a); if (!c.has(k)) c.set(k, fn(...a)); return c.get(k); }; }; const add = memo((a, b) => { console.log("compute"); return a + b; }); console.log(add(2, 3), add(2, 3), add(5, 5)); - Why is the dynamic
import()good for performance?✨ Show Answer
Answer: It splits your bundle. The slow chart library or markdown renderer doesn't ship in the initial JS — it loads only when the user actually opens that screen. Smaller initial bundle = faster first paint, especially on slow networks.
- In one paragraph, why is performance especially critical in Bangladesh?
✨ Show Answer
Answer: A large fraction of users are on mid-range Androids over 3G or weak 4G — every extra kilobyte and JS tick is a felt delay. Apps that ignore performance bleed users in week one. Debounce inputs, lazy-load heavy chunks, ship images via
loading="lazy", and keep the main thread under 50 ms tasks.
Summary — Module 36
Debounce for "wait for silence", throttle for "at most every X ms", memoize for repeat work. Use rAF for visuals. Lazy-load heavy modules. Measure with the Performance API. On the typical Bangladeshi device, every saving is felt.