1. Problems 81–90
Same format as the previous modules: expand a problem to see the approach and both commented solutions.
JavaScript lets an inner function reassign an outer variable with no special syntax. Python requires nonlocal before doing that — without it, count += 1 inside a nested function would silently create a brand-new local variable instead of modifying the outer one. Watch for it in the examples below.
P81
Counter Using Closure
Each call to the returned function increments a hidden count.
Counter Using Closure
Each call to the returned function increments a hidden count.
Approach: the outer function declares a variable and returns an inner function that reads and updates it. The inner function keeps a private reference to that variable — a closure — even after the outer function has already finished running.
JavaScript
// The returned function "closes over" `count` -- it keeps a private reference
// to that variable even after createCounter() has finished running.
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Python
def create_counter():
"""A nested function reads and modifies the enclosing variable via `nonlocal`."""
count = 0
def increment():
nonlocal count # without this, count += 1 below would create a new local variable
count += 1
return count
return increment
counter = create_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
P82
Bank Account Using Closure (Private State)
The balance can only change through deposit/withdraw, never directly.
Bank Account Using Closure (Private State)
The balance can only change through deposit/withdraw, never directly.
Approach: the balance variable lives only inside the closure's scope. The only way to read or change it is through the functions returned alongside it — which is what "private" means here, without a real access-modifier keyword.
JavaScript
// balance is never exposed directly -- only these two functions can touch it,
// which is what "private" means without a real access-modifier keyword.
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) { balance += amount; return balance; },
withdraw(amount) {
if (amount > balance) throw new Error("Insufficient funds");
balance -= amount;
return balance;
},
getBalance() { return balance; },
};
}
const account = createBankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(account.getBalance()); // 120
Python
def create_bank_account(initial_balance: float):
"""Same idea: `balance` only exists inside this closure's scope."""
balance = initial_balance
def deposit(amount):
nonlocal balance
balance += amount
return balance
def withdraw(amount):
nonlocal balance
if amount > balance:
raise ValueError("Insufficient funds")
balance -= amount
return balance
def get_balance():
return balance
return {"deposit": deposit, "withdraw": withdraw, "get_balance": get_balance}
account = create_bank_account(100)
account["deposit"](50)
account["withdraw"](30)
print(account["get_balance"]()) # 120
P83
Function Currying
Turn add(a,b,c) into add(a)(b)(c) — one argument per call.
Function Currying
Turn add(a,b,c) into add(a)(b)(c) — one argument per call.
Approach: each call collects one more argument into a closure. Once enough arguments have accumulated, the original function finally runs with all of them; until then, every call just returns another function waiting for the rest.
JavaScript
// Curry turns add(a, b, c) into add(a)(b)(c) -- each call captures one more
// argument in a closure until all three have been supplied.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args); // enough args -- call it
return (...more) => curried(...args, ...more); // not enough yet -- keep collecting
};
}
function add3(a, b, c) { return a + b + c; }
const curriedAdd = curry(add3);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
Python
def curry(fn, arity):
"""Python has no fn.length, so the expected argument count is passed in explicitly."""
def curried(*args):
if len(args) >= arity:
return fn(*args) # enough args -- call it
return lambda *more: curried(*args, *more) # not enough yet -- keep collecting
return curried
def add3(a, b, c):
return a + b + c
curried_add = curry(add3, 3)
print(curried_add(1)(2)(3)) # 6
print(curried_add(1, 2)(3)) # 6
P84
once() — Run a Function Only Once
Later calls return the cached result instead of running again.
once() — Run a Function Only Once
Later calls return the cached result instead of running again.
Approach: a flag and a result variable, both closed over — the first call runs the real function and saves its result; every call after that just returns what was saved, skipping the real function entirely.
JavaScript
// A flag inside the closure remembers whether the wrapped function has already
// run; every call after the first just returns the cached result.
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
result = fn(...args);
called = true;
}
return result;
};
}
const initialize = once(() => { console.log("Initializing..."); return "ready"; });
console.log(initialize()); // logs "Initializing...", returns "ready"
console.log(initialize()); // logs nothing, returns "ready" again
Python
def once(fn):
"""Same flag-and-cache trick, using a mutable dict since Python closures can't
reassign an outer variable without `nonlocal` (a dict's contents don't need it)."""
state = {"called": False, "result": None}
def wrapper(*args, **kwargs):
if not state["called"]:
state["result"] = fn(*args, **kwargs)
state["called"] = True
return state["result"]
return wrapper
def _initialize():
print("Initializing...")
return "ready"
initialize = once(_initialize)
print(initialize()) # logs "Initializing...", returns "ready"
print(initialize()) # logs nothing, returns "ready" again
P85
memoize() — Cache Results by Argument
An expensive call only ever runs once per distinct input.
memoize() — Cache Results by Argument
An expensive call only ever runs once per distinct input.
Approach: like once(), but keyed by argument instead of a single flag — a cache (Map/dict) remembers the result for each distinct set of arguments already seen, and skips recomputation on a repeat.
JavaScript
// Store each call's result keyed by its (stringified) arguments -- an expensive
// call only ever runs once per distinct input.
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key); // seen these args before -- skip the work
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const slowSquare = (n) => { for (let i = 0; i < 1e6; i++); return n * n; }; // pretend this is slow
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // computed
console.log(fastSquare(5)); // returned instantly from cache
Python
def memoize(fn):
cache = {}
def wrapper(*args):
if args in cache: # a tuple of args works directly as a dict key in Python
return cache[args]
result = fn(*args)
cache[args] = result
return result
return wrapper
def _slow_square(n):
for _ in range(1_000_000): # pretend this is slow
pass
return n * n
fast_square = memoize(_slow_square)
print(fast_square(5)) # computed
print(fast_square(5)) # returned instantly from cache
# Idiomatic Python: functools.lru_cache does this automatically as a decorator.
P86
debounce() — Wait for a Pause Before Running
Only the last call in a rapid burst actually fires.
debounce() — Wait for a Pause Before Running
Only the last call in a rapid burst actually fires.
Approach: every call cancels whatever timer the previous call scheduled, then starts a fresh one — the wrapped function only actually runs once calls stop arriving for the full delay. Perfect for a search box: don't hit the API on every keystroke, only once typing pauses.
JavaScript
// Every call cancels the previous pending timer and starts a new one -- the
// wrapped function only actually runs once calls stop arriving for `delay` ms.
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer); // cancel whatever was scheduled before
timer = setTimeout(() => fn(...args), delay);
};
}
const search = debounce((value) => console.log("API:", value), 500);
search("h");
search("he");
search("hel"); // only this final call actually fires, 500ms after it's made
Python
import threading
def debounce(fn, delay_seconds):
"""Python has no setTimeout, so a Timer thread stands in for the browser's timer."""
state = {"timer": None}
def wrapper(*args, **kwargs):
if state["timer"] is not None:
state["timer"].cancel() # cancel whatever was scheduled before
state["timer"] = threading.Timer(delay_seconds, lambda: fn(*args, **kwargs))
state["timer"].start()
return wrapper
search = debounce(lambda value: print("API:", value), 0.5)
search("h")
search("he")
search("hel") # only this final call actually fires, 0.5s after it's made
P87
throttle() — Limit How Often a Function Can Run
Guarantees at most one run per interval, unlike debounce.
throttle() — Limit How Often a Function Can Run
Guarantees at most one run per interval, unlike debounce.
Approach: remember the timestamp of the last allowed call. A new call only goes through once enough time has passed since then — unlike debounce, throttle keeps firing steadily through a continuous stream of calls instead of waiting for them to stop. Better fit for a scroll handler.
JavaScript
// Unlike debounce, throttle guarantees the function runs at most once per
// interval, even if calls keep arriving the whole time.
function throttle(fn, interval) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
fn(...args); // enough time has passed -- allow this call through
}
};
}
const onScroll = throttle(() => console.log("scroll handled"), 1000);
// Rapid-fire calls to onScroll() over 3 seconds would log roughly 3 times, not dozens.
Python
import time
def throttle(fn, interval_seconds):
state = {"last_call": 0}
def wrapper(*args, **kwargs):
now = time.monotonic()
if now - state["last_call"] >= interval_seconds:
state["last_call"] = now
fn(*args, **kwargs) # enough time has passed -- allow this call through
return wrapper
on_scroll = throttle(lambda: print("scroll handled"), 1.0)
# Rapid-fire calls to on_scroll() over 3 seconds would log roughly 3 times, not dozens.
P88
retry() — Retry a Failing Function N Times
Call it again on failure, up to a limit, before giving up.
retry() — Retry a Failing Function N Times
Call it again on failure, up to a limit, before giving up.
Approach: loop up to maxAttempts times. If the call succeeds, return immediately; if it throws and attempts remain, log it and loop again; if the final attempt also fails, let the error escape for real.
JavaScript
// Call the function; if it throws, try again up to `maxAttempts` times before
// finally letting the error escape.
function retry(fn, maxAttempts) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return fn(); // success -- stop retrying
} catch (err) {
if (attempt === maxAttempts) throw err; // out of attempts -- give up
console.log(`Attempt ${attempt} failed, retrying...`);
}
}
}
let tries = 0;
const flaky = () => { tries++; if (tries < 3) throw new Error("fail"); return "success"; };
console.log(retry(flaky, 5)); // "success" (after two logged retries)
Python
def retry(fn, max_attempts: int):
for attempt in range(1, max_attempts + 1):
try:
return fn() # success -- stop retrying
except Exception as err:
if attempt == max_attempts:
raise # out of attempts -- give up
print(f"Attempt {attempt} failed, retrying...")
tries = 0
def flaky():
global tries
tries += 1
if tries < 3:
raise ValueError("fail")
return "success"
print(retry(flaky, 5)) # "success" (after two printed retries)
P89
pipe() and compose() — Chain Functions Together
pipe runs left to right; compose runs right to left.
pipe() and compose() — Chain Functions Together
pipe runs left to right; compose runs right to left.
Approach: both take a list of single-argument functions and return one new function that threads a value through all of them, in the given order (pipe) or the reverse order (compose) — a direct application of Module 6's reduce.
JavaScript
// pipe() runs functions left to right; compose() runs them right to left.
// Both thread one value through a list of single-argument functions.
function pipe(...fns) {
return (initial) => fns.reduce((value, fn) => fn(value), initial);
}
function compose(...fns) {
return (initial) => fns.reduceRight((value, fn) => fn(value), initial);
}
const double = (n) => n * 2;
const addOne = (n) => n + 1;
console.log(pipe(double, addOne)(5)); // (5*2)+1 = 11
console.log(compose(double, addOne)(5)); // (5+1)*2 = 12
Python
from functools import reduce
def pipe(*fns):
"""Runs functions left to right, threading one value through all of them."""
return lambda initial: reduce(lambda value, fn: fn(value), fns, initial)
def compose(*fns):
"""Runs functions right to left."""
return lambda initial: reduce(lambda value, fn: fn(value), reversed(fns), initial)
double = lambda n: n * 2
add_one = lambda n: n + 1
print(pipe(double, add_one)(5)) # (5*2)+1 = 11
print(compose(double, add_one)(5)) # (5+1)*2 = 12
P90
Partial Application
Pre-fill some arguments, get back a smaller function.
Partial Application
Pre-fill some arguments, get back a smaller function.
Approach: similar to currying, but simpler — fix some arguments up front in a closure, and return a new function that only needs the remaining ones to actually call the original.
JavaScript
// A partially applied function pre-fills some arguments and returns a new,
// smaller function waiting for the rest.
function partial(fn, ...presetArgs) {
return (...remainingArgs) => fn(...presetArgs, ...remainingArgs);
}
function greet(greeting, name) { return `${greeting}, ${name}!`; }
const sayHello = partial(greet, "Hello");
console.log(sayHello("Amit")); // "Hello, Amit!"
Python
def partial(fn, *preset_args):
"""Same idea; Python's standard library even ships this as functools.partial."""
return lambda *remaining_args: fn(*preset_args, *remaining_args)
def greet(greeting, name):
return f"{greeting}, {name}!"
say_hello = partial(greet, "Hello")
print(say_hello("Amit")) # "Hello, Amit!"
# Idiomatic Python: from functools import partial
2. Key Takeaways
- A closure is just a function plus the variables it can still reach from where it was defined — every problem here (counter, private state, cache, timer) is that same mechanism used for a different job.
- debounce waits for a pause and fires once; throttle fires steadily no more than once per interval. Mixing them up is a very common interview mistake.
- Currying, partial application, memoization, pipe and compose are all thin wrappers that take a function in and hand a new function back out — a pattern worth recognizing on sight, since interview "implement X" questions usually turn out to be one of these in disguise.