Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🛡️ Days 23–27 – Professional JavaScript  ·  Day 24

Day 24: Higher-Order Functions, Debounce & Throttle

⏱ 95 min📶 Pro🎯 100% self-study
Day 24 / 30

✅ Today you will learn

  • Higher-order functions: take or return functions
  • Composition, pipe and currying
  • Immutability: update data without mutating it
  • Debounce: wait until the user stops typing
  • Throttle: run at most once every N ms

🏢 In the real world

The live search box calls the API on every keystroke: typing “kaju katli” fires 10 requests, results flicker, and on a shared server the shop starts hitting rate limits. Debounce fixes it with 10 lines – and “implement debounce” is one of the most common front-end interview coding questions in India.

Higher-order functions

JavaScript
// Takes a function
function repeat(times, action) { for (let i = 1; i <= times; i++) action(i); }
repeat(3, n => console.log(`Box ${n} packed`));

// Returns a function
const discount = percent => price => Math.round(price * (1 - percent / 100));
const festival = discount(15);
console.log([900, 650, 500].map(festival));

// Wraps a function to add behaviour (a "decorator")
function logged(fn) {
  return (...args) => {
    const result = fn(...args);
    console.log(`${fn.name}(${args.join(', ')}) → ${result}`);
    return result;
  };
}
const addGst = function addGst(amount) { return Math.round(amount * 1.05); };
logged(addGst)(1800);
Box 1 packed Box 2 packed Box 3 packed [ 765, 553, 425 ] addGst(1800) → 1890

Composition, pipe and currying

JavaScript
const pipe = (...fns) => input => fns.reduce((value, fn) => fn(value), input);

const subtotal = cart => cart.reduce((s, i) => s + i.price * i.qty, 0);
const applyCoupon = code => amount => (code === 'SWEET10' ? amount * 0.9 : amount);
const addDelivery = amount => (amount >= 1500 ? amount : amount + 60);
const round = amount => Math.round(amount);

const checkoutTotal = pipe(subtotal, applyCoupon('SWEET10'), addDelivery, round);
console.log(checkoutTotal([{ price: 900, qty: 2 }]));       // 1800 → 1620 → 1620
console.log(checkoutTotal([{ price: 500, qty: 1 }]));       // 500 → 450 → 510

// currying: f(a, b, c) → f(a)(b)(c)
const curry = fn => function c(...args) { return args.length >= fn.length ? fn(...args) : (...more) => c(...args, ...more); };
const price = curry((rate, kg, gst) => Math.round(rate * kg * (1 + gst)));
const kajuPrice = price(900);
console.log(kajuPrice(2)(0.05), kajuPrice(0.5, 0.05));
1620 510 1890 473

Immutability: never change data in place

JavaScript
const cart = { customer: 'Aarti', items: [{ id: 1, qty: 1 }, { id: 4, qty: 2 }] };

// ❌ mutating – other parts of the app holding "cart" change silently
// cart.items[0].qty = 5;

// ✅ create new objects/arrays for what changed
const updated = {
  ...cart,
  items: cart.items.map(item => (item.id === 1 ? { ...item, qty: item.qty + 1 } : item)),
};
const removed = { ...cart, items: cart.items.filter(i => i.id !== 4) };
const added = { ...cart, items: [...cart.items, { id: 7, qty: 1 }] };

console.log(cart.items[0].qty, updated.items[0].qty);
console.log(removed.items.length, added.items.length, cart.items.length);
console.log(cart.items[1] === updated.items[1]);   // unchanged parts are shared (cheap)

const deepCopy = structuredClone(cart);              // full independent copy when needed
deepCopy.items[0].qty = 99;
console.log(cart.items[0].qty);
1 2 1 3 2 true 1
🧠 Why React cares

React decides whether to re-render by comparing references (old === new). If you mutate an array, the reference stays the same and the screen does not update. Immutable updates are therefore a must for React and Redux.

Debounce: wait for a pause

JavaScript
function debounce(fn, delay) {
  let timer;                                   // closure (Day 16)
  return function (...args) {
    clearTimeout(timer);                       // every new call cancels the previous one
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const search = debounce(q => console.log(`🔎 API call for "${q}"`), 300);

// simulate typing "kaju" quickly, a pause, then " katli"
const typed = ['k', 'ka', 'kaj', 'kaju', 'kaju ', 'kaju k', 'kaju ka', 'kaju katli'];
const times = [0, 80, 160, 240, 700, 780, 860, 940];
typed.forEach((text, i) => setTimeout(() => search(text), times[i]));
// only 2 API calls instead of 8
🔎 API call for "kaju" 🔎 API call for "kaju katli"

Throttle: at most once per interval

JavaScript
function throttle(fn, interval) {
  let last = 0, timer;
  return function (...args) {
    const now = Date.now();
    const remaining = interval - (now - last);
    if (remaining <= 0) {
      last = now;
      fn.apply(this, args);                    // run immediately
    } else {
      clearTimeout(timer);
      timer = setTimeout(() => { last = Date.now(); fn.apply(this, args); }, remaining);   // and once at the end
    }
  };
}

let calls = 0;
const onScroll = throttle(y => console.log(`update sticky header at y=${y} (call ${++calls})`), 200);
for (let i = 0; i <= 20; i++) setTimeout(() => onScroll(i * 50), i * 30);   // 21 scroll events over 600 ms
update sticky header at y=0 (call 1) update sticky header at y=300 (call 2) update sticky header at y=650 (call 3) update sticky header at y=1000 (call 4)
DebounceThrottle
RunsOnce, after events stop for N msRegularly, at most once per N ms
LikeA lift door waiting until nobody else entersA traffic signal letting cars through every N seconds
Use forSearch boxes, auto-save, resize-end, form validationScroll position, mousemove, drag, analytics pings
Typical delay250–400 ms100–250 ms

Real task: debounced live search against the API

HTML + JavaScript
<input id="q" placeholder="Type fast: kaju, ghewar, box…" aria-label="Search sweets" style="width:260px">
<p>API calls: <b id="calls">0</b> · keystrokes: <b id="keys">0</b></p>
<ul id="results" aria-live="polite"></ul>

<script>
const API = 'https://codingeasily.in/api/sweets/';
let calls = 0, keys = 0, latest = 0;

function debounce(fn, delay) {
  let t;
  return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), delay); };
}

async function search(q) {
  const id = ++latest;                                // ignore out-of-order responses
  document.querySelector('#calls').textContent = ++calls;
  const res = await fetch(`${API}?q=${encodeURIComponent(q)}`);
  const data = await res.json();
  if (id !== latest) return;                          // a newer search already started
  document.querySelector('#results').innerHTML =
    data.map(p => `<li>${p.name} – ₹${p.price}</li>`).join('') || '<li>No match</li>';
}

const debounced = debounce(q => { if (q.trim()) search(q.trim()); }, 300);
document.querySelector('#q').addEventListener('input', e => {
  document.querySelector('#keys').textContent = ++keys;
  debounced(e.target.value);
});
</script>
▶  Type quickly – keystrokes climb, API calls stay low

🏅 Job skills unlocked today

✔ Write and use higher-order functions and decorators✔ Compose logic with pipe and currying✔ Update objects and arrays immutably✔ Implement debounce and throttle and choose between them

Practice tasks

🏋️ Practice – do it yourself

Task 1: Write memoizeAsync(fn) that caches results of an async function by its argument so repeated searches do not call the API again.
Show solution
JavaScript
function memoizeAsync(fn) {
  const cache = new Map();
  return arg => {
    if (!cache.has(arg)) cache.set(arg, fn(arg).catch(err => { cache.delete(arg); throw err; }));
    return cache.get(arg);
  };
}
let apiCalls = 0;
const fakeApi = q => new Promise(r => setTimeout(() => r(`results for ${q} (call ${++apiCalls})`), 50));
const cachedSearch = memoizeAsync(fakeApi);
(async () => {
  console.log(await cachedSearch('kaju'));
  console.log(await cachedSearch('kaju'));
  console.log(await cachedSearch('ghewar'));
})();
results for kaju (call 1) results for kaju (call 1) results for ghewar (call 2)
Task 2: Immutably update a nested order: change the qty of item id 4 to 3 and add a “gift” flag, without touching the original.
Show solution
JavaScript
const order = { id: 1042, customer: { name: 'Aarti' }, items: [{ id: 1, qty: 1 }, { id: 4, qty: 1 }] };
const next = {
  ...order,
  gift: true,
  items: order.items.map(i => (i.id === 4 ? { ...i, qty: 3 } : i)),
};
console.log(JSON.stringify(order));
console.log(JSON.stringify(next));
console.log(order.customer === next.customer);
{"id":1042,"customer":{"name":"Aarti"},"items":[{"id":1,"qty":1},{"id":4,"qty":1}]} {"id":1042,"customer":{"name":"Aarti"},"items":[{"id":1,"qty":1},{"id":4,"qty":3}],"gift":true} true
Task 3: Add a leading option to debounce: debounce(fn, ms, { leading: true }) runs on the first call immediately, then waits.

Quick quiz

🧠 Quick Quiz – test yourself

1. A higher-order function…
map, filter, debounce…
2. Best for a search box:
Wait for a pause.
3. Best for scroll position updates:
Regular updates.
4. Which update is immutable?
New array.
5. debounce relies on…
The timer lives in a closure.

Interview corner

💼 Interview Corner

Implement debounce.

function debounce(fn, ms) { let t; return function (...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), ms); }; } – each call resets the timer so fn runs only after calls stop for ms; mention leading/trailing options and cancel().

Debounce vs throttle with a real example?

Debounce: autocomplete search – fire once after typing stops. Throttle: infinite-scroll position or window resize handling – fire at a steady maximum rate while the event continues.

Summary

  • Functions are values: pass them, return them, wrap them.
  • pipe/compose and currying build logic from small pieces.
  • Update with spread/map/filter; structuredClone for deep copies.
  • Debounce for “after the pause”, throttle for “at most every N ms”.
➡️ Next

Day 25: DOM performance and patterns – fast rendering, event delegation at scale, requestAnimationFrame and lazy loading.

Frequently Asked Questions

Should I use lodash for debounce?
In real projects lodash.debounce/throttle are well-tested and include edge cases. Knowing how to write them yourself is what interviews test.
Is functional programming better than OOP?
Neither – modern JavaScript mixes both: classes for some models, small pure functions and immutable data for logic and UI state.
← JavaScript (30 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 25: DOM Performance & Patterns Your progress: 24 / 30 (80%) · Lesson 25 / 30 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free