Performance — Debounce, Throttle, Memoize

3G নেটওয়ার্ক ও সস্তা ফোনেও smooth app

~35 min Intermediate 10 practice problems Live runner

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.

Bangladesh-এর অধিকাংশ user low-end Android + 3G-তে browse করে। Performance optimization এখানে অপরিহার্য — শুধু "nice to have" নয়।

2. Debounce — wait for silence

Use case: search-as-you-type. Run the function only after the user pauses for X ms.

debounce.js
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.

throttle.js
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).
PatternFiresUse For
DebounceAfter silenceSearch box, autosave
ThrottleAt most every X msScroll, 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

memo.js
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

perf.js
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 (শব্দকোষ)

TermMeaningবাংলায়
DebounceRun only after a quiet pause — best for search-as-you-type.চুপ হলে চলে — search-এ ভালো।
ThrottleRun at most every N ms — best for scroll/drag/resize.প্রতি N ms-এ একবার — scroll-এ ভালো।
MemoizeCache function results by their input arguments.Input অনুযায়ী result cache।
requestAnimationFrameSchedules a callback before the next repaint (~60 fps).পরের paint-এর আগে চলে — ~60 fps।
Lazy loadFetch resources only when needed (image / module).প্রয়োজনে load করা।
Code splittingSplit the bundle so users download only what they use.Bundle ভাগ করে যা দরকার সেটাই পাঠানো।
performance.nowHigh-resolution timestamp for measurements.High-res timestamp — measurement-এর জন্য।
Long taskSync work over ~50 ms — visibly stutters the UI.~50 ms-এর বেশি sync কাজ — UI freeze।
Critical pathThe first set of bytes needed before the page is usable.প্রথমে usable হতে যা byte লাগে।
সংক্ষেপে: debounce wait-for-silence, throttle steady-but-rate-limited, memoize repeat-work cache, rAF smooth-paint। Bangladesh-এর low-end Android + 3G-তে এই চারটি utility না থাকলে app চরম slow হয়। loading="lazy" img-এ যোগ করা সবচেয়ে সহজ ১-line optimization।

9. Practice Problems

  1. Build debounce and demonstrate with rapid calls.
    ✨ Show Answer
    a1.js
    const 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");
  2. Build throttle.
    ✨ Show Answer
    a2.js
    const 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);
  3. Memoize a slow square function and prove the cache works.
    ✨ Show Answer
    a3.js
    const 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);
  4. 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.

  5. Time how long sorting 100k random numbers takes.
    ✨ Show Answer
    a5.js
    const 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");
  6. 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));
  7. Why does requestAnimationFrame beat setInterval(..., 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. setInterval keeps firing regardless and drifts off the frame boundary.

  8. Memoize a multi-arg function with a stringified key.
    ✨ Show Answer
    a8.js
    const 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));
  9. 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.

  10. 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.

debounce, throttle, memoize, rAF — চারটি utility আপনার app কে instantly দ্রুত করবে। Performance API দিয়ে measure করুন।

Next Module → Testing।