Functional Programming
Pure functions, currying, partial application, composition, memoization, mixins, and function arity.
11 questions
JuniorTheoryCommonWhat is memoization, and when does it speed a function up?
What is memoization, and when does it speed a function up?
Memoization caches a function's result keyed by its arguments, so a repeat call with the same inputs returns the stored value instead of recomputing. It only helps for pure functions whose result depends solely on arguments, and pays off when the same inputs recur and the computation is expensive. It trades memory for speed, so caching cheap or rarely-repeated calls just wastes space.
Common mistakes
- ✗Memoizing impure functions whose output depends on external state, returning stale values
- ✗Believing memoization is always a win, ignoring its memory cost
- ✗Caching cheap or never-repeated calls where lookup overhead outweighs the saving
Follow-up questions
- →Why must a memoized function be pure for the cache to stay correct?
- →How does an unbounded memoization cache risk a memory leak?
JuniorTheoryCommonWhat is a pure function?
What is a pure function?
A pure function returns the same output for the same inputs and produces no side effects — it does not mutate external state, do I/O, or depend on anything but its arguments. For example, (a, b) => a + b is pure, while one that pushes to an outer array or reads Date.now() is impure. Purity makes code predictable and easy to test.
Common mistakes
- ✗Thinking a function that mutates its arguments or an outer variable can still be pure
- ✗Believing purity is about performance or caching rather than determinism and no side effects
Follow-up questions
- →Is a function that calls
Date.now()pure, and why? - →How does purity make a function easier to unit-test?
JuniorTheoryCommonHow do rest parameters differ from the arguments object?
How do rest parameters differ from the arguments object?
A rest parameter ...args collects the remaining arguments into a real Array, so array methods like map work directly, and it holds only the named-after parameters. The arguments object is array-like but not an array, lacks those methods, and always holds every argument. Arrow functions have no arguments, so rest is the modern choice.
Common mistakes
- ✗Believing
argumentsis a real array and supports methods likemaporfilter - ✗Forgetting that arrow functions have no
argumentsobject, so only rest works there
Follow-up questions
- →How would you convert the
argumentsobject into a real array? - →Why can a function have only one rest parameter, and must it come last?
MiddleTheoryOccasionalWhat do the functional helpers compose and pipe do?
What do the functional helpers compose and pipe do?
Both combine several functions so one's output feeds the next's input, producing a single new function. They differ only in direction: compose(f, g)(x) runs right-to-left as f(g(x)), while pipe(f, g)(x) runs left-to-right as g(f(x)). They are not built in, and they work best with pure single-argument functions.
Common mistakes
- ✗Mixing up the direction —
composeis right-to-left,pipeis left-to-right - ✗Assuming
composeandpipeare built-in globals rather than helpers you write or import
Follow-up questions
- →Why do composed functions work best when each is pure and unary?
- →How would you write
pipeusingreduce?
MiddleTheoryOccasionalWhat is currying?
What is currying?
Currying transforms a function of many arguments into a chain of nested functions that each take exactly one argument. So (a, b, c) => a + b + c becomes a => b => c => a + b + c, called as f(1)(2)(3). Each call captures its argument in a closure and returns the next function, enabling partial application and reuse.
Common mistakes
- ✗Confusing currying with passing all arguments at once as an array
- ✗Thinking currying caches results like memoization rather than splitting arguments
Follow-up questions
- →What language feature lets a curried chain remember earlier arguments?
- →How does currying enable partial application of a function?
MiddleTheoryOccasionalHow do you implement memoization with a closure and a Map cache?
How do you implement memoization with a closure and a Map cache?
Wrap the function and hold a Map in the closure; on each call build a cache key from the arguments, return the cached value if present, otherwise compute, store, and return. The pitfall is the key: objects and arrays stringify to the same [object Object], so keying naively collides distinct inputs, and JSON.stringify ignores argument order across keys and drops functions. A Map allows non-string keys but only matches objects by identity.
Common mistakes
- ✗Keying on object arguments naively so distinct objects collide as
[object Object] - ✗Assuming a
Mapmatches object keys by content rather than identity - ✗Trusting
JSON.stringifykeys despite property-order and function-dropping flaws
Follow-up questions
- →How would you build a stable cache key for multiple object arguments?
- →Why might a
WeakMapcache help avoid leaking memoized object keys?
MiddleTheoryOccasionalWhat is partial application, and how does it differ from currying?
What is partial application, and how does it differ from currying?
Partial application fixes some of a function's arguments up front, producing a new function that takes the rest; fn.bind(null, 1) pre-fills the first argument. Currying instead transforms an n-ary function into a chain of single-argument functions, each returning the next. So partial application supplies any number of args once and returns one function, while currying always takes one arg at a time across many calls.
Common mistakes
- ✗Treating partial application and currying as the same thing
- ✗Thinking
bindruns the function immediately rather than returning a new one - ✗Assuming partial application must supply exactly one argument per call
Follow-up questions
- →How does
fn.bindsetthiswhile pre-filling leading arguments? - →Why can a curried function be called one argument at a time across calls?
JuniorTheoryRareWhat is a function's arity, and what does fn.length report?
What is a function's arity, and what does fn.length report?
Arity is the number of parameters a function declares. fn.length counts only the parameters before the first one with a default value or the rest parameter, so (a, b) => {} is 2 but (a, b = 1, ...r) => {} is 1. A parameter is the named slot in the declaration; an argument is the actual value passed at call time. Functions with no name are anonymous.
Common mistakes
- ✗Thinking
fn.lengthcounts arguments passed at call time rather than declared parameters - ✗Assuming default-valued and rest parameters are included in
fn.length - ✗Using parameter and argument interchangeably when they name different things
Follow-up questions
- →How do you read the actual arguments passed, given
fn.lengthignores rest? - →Why does
arguments.lengthdiffer fromfn.lengthfor the same call?
MiddleTheoryRareWhat are mixins, and how do they compose behaviour onto a prototype?
What are mixins, and how do they compose behaviour onto a prototype?
A mixin is an object of reusable methods copied onto a target with Object.assign(Class.prototype, mixin), letting many unrelated classes share behaviour without a common ancestor. Unlike inheritance, which gives one linear prototype chain and an is-a relationship, mixins compose several independent capabilities and express has-a. The cost is that copied methods overwrite same-named ones silently, with no super to reach the original.
Common mistakes
- ✗Thinking mixins create an
is-ainheritance link rather than copying methods - ✗Expecting
superto reach a method that a mixin overwrote - ✗Forgetting
Object.assignsilently overwrites same-named target methods
Follow-up questions
- →Why does mixin order matter when two mixins define the same method?
- →How does a functional mixin factory let you parametrise the copied behaviour?
SeniorTheoryRareWhat do referential transparency, side effects, and point-free style mean?
What do referential transparency, side effects, and point-free style mean?
Referential transparency means an expression can be replaced by its value without changing the program — pure functions are transparent, so add(2, 3) can become 5. A side effect is any state change beyond the return value: I/O, DOM edits, mutating inputs, Math.random(), or reading the clock. Point-free style composes functions without naming their arguments, building behaviour from small pure pieces.
Common mistakes
- ✗Calling an impure function referentially transparent because it still returns a value
- ✗Thinking mutating an argument or reading the clock is not a side effect
Follow-up questions
- →Why is a function that calls
Math.random()not referentially transparent? - →How do currying and composition make point-free style practical?
SeniorTheoryRareWhat are thunks and point-free style, and when do memoization and partial application hurt?
What are thunks and point-free style, and when do memoization and partial application hurt?
A thunk is a zero-argument function wrapping a deferred computation, so the work runs only when called — useful for lazy evaluation. The functional technique point-free style builds functions by composing others without naming the arguments. Memoization hurts when inputs rarely repeat or are hard to key, growing an unbounded cache for no gain. Partial application hurts when it hides argument order or fixes the wrong leading slot, making call sites obscure.
Common mistakes
- ✗Thinking a thunk runs its work eagerly rather than deferring until called
- ✗Believing memoization is harmless even when inputs rarely repeat
- ✗Assuming partial application always clarifies rather than obscuring call sites
Follow-up questions
- →How does a thunk enable lazy evaluation of an expensive default value?
- →When does an unbounded memoization cache become a memory leak in a long-running app?