Functional Programming

10 questions found

What is a pure function?

Beginner
A pure function always returns the same output for the same input and produces no observable side effects (no mutating external state, no I/O), which makes it predictable, testable, and easy to reason about in isolation.
// Pure
function add(a, b) { return a + b; }

// Impure — depends on and mutates external state
let total = 0;
function addToTotal(n) { total += n; }
Real-world example Preferring pure calculation functions in a reducer or utility layer for predictable, easily-testable behavior.

Common follow-ups: Can a pure function still throw an error?

Error Handling

What does 'immutability' mean and why does functional code favor it?

Beginner
Immutability means not modifying data after it's created — instead, you produce new data structures with the desired changes. This avoids bugs from shared mutable state and makes it easier to track how and when data changes.
// Mutating (avoid)
const arr = [1,2,3];
arr.push(4);

// Immutable (preferred)
const arr2 = [...arr, 4];
Real-world example Avoiding accidental shared-state bugs in React, where mutating state directly can cause missed re-renders.

Common follow-ups: Does immutability have a performance cost, and how is it usually managed?

Destructuring Spread & Rest

What is a higher-order function?

Beginner
A higher-order function either accepts one or more functions as arguments, returns a function, or both — array methods like map, filter, and reduce are common built-in examples.
function multiplyBy(factor) {
  return (n) => n * factor; // returns a function
}
const double = multiplyBy(2);
double(5); // 10
Real-world example Building a configurable validation function generator, like requireMinLength(5).

Common follow-ups: What's the difference between a higher-order function and a callback?

Closures

What is function composition and how do you implement it?

Intermediate
Composition combines multiple small functions into one, where the output of each becomes the input to the next — typically implemented with a compose() or pipe() helper that reduces over an array of functions.
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
const addOne = n => n + 1;
const double = n => n * 2;
const transform = pipe(addOne, double);
transform(3); // (3+1)*2 = 8
Real-world example Building a data-processing pipeline that trims, validates, then formats user input in clearly separated steps.

Common follow-ups: What's the difference between pipe() and compose() in terms of execution order?

Destructuring Spread & Rest

What is currying and what problem does it solve?

Intermediate
Currying transforms a function taking multiple arguments into a sequence of functions each taking a single argument, allowing you to partially apply some arguments early and reuse the resulting specialized function later.
const curriedAdd = (a) => (b) => (c) => a + b + c;
const addFive = curriedAdd(5);
addFive(2)(3); // 10
Real-world example Creating a reusable, pre-configured validator like isLongerThan(8) from a generic two-argument comparison function.

Common follow-ups: How does currying relate to partial application, and are they the same thing?

Closures

Why are recursion and immutable data often used together in functional style, instead of loops with mutable counters?

Intermediate
Recursion naturally avoids mutable loop counters and accumulator variables by passing updated state as arguments to each recursive call, keeping each step's data immutable and self-contained — matching the broader functional preference for avoiding shared mutable state.
function sum(arr, acc = 0) {
  if (arr.length === 0) return acc;
  const [first, ...rest] = arr;
  return sum(rest, acc + first); // no mutable counter
}
sum([1,2,3]); // 6
Real-world example Processing a linked-list-like or tree-like data structure where a recursive, immutable approach mirrors its shape naturally.

Common follow-ups: What's the risk of using deep recursion in JavaScript, given it lacks guaranteed tail-call optimization?

Error Handling

What does 'referential transparency' mean, and how does it enable optimizations like memoization?

Advanced
An expression is referentially transparent if it can be replaced with its computed value without changing the program's behavior — true only for pure functions with no side effects. This property is exactly what makes memoization (caching results by input) safe: you can trust that the same input always deserves the same cached output.
function memoize(fn) {
  const cache = new Map();
  return (arg) => {
    if (cache.has(arg)) return cache.get(arg);
    const result = fn(arg); // safe to cache only if fn is pure
    cache.set(arg, result);
    return result;
  };
}
Real-world example Memoizing an expensive pure calculation, like a Fibonacci function or a complex pricing formula.

Common follow-ups: Why would memoizing an impure function (one with side effects or external dependencies) be dangerous?

Map Set WeakMap & WeakSet

How do you implement a debounce function using closures, and how is it a functional programming technique?

Advanced
debounce() returns a new function that wraps a closure over a timer variable, delaying the original function's execution until a pause in calls occurs — it's a higher-order function that takes a function and returns an enhanced function, a core FP pattern.
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}
const debouncedSearch = debounce(runSearch, 300);
Real-world example Delaying an API search request until the user stops typing for 300ms, instead of firing on every keystroke.

Common follow-ups: How does debounce differ from throttle, and when would you choose each?

Performance Optimization: Debouncing Throttling & Memoization

What are functors and monads, at a practical level, and does JavaScript have native support for them?

Advanced
A functor is any structure with a .map() method that applies a function to its wrapped value while keeping the wrapper (Array and Promise both qualify). A monad additionally has a way to 'flatten' nested wrappers (like Promise's automatic chaining, or Array's flatMap). JavaScript doesn't enforce these as formal types, but Array and Promise both behave like a functor/monad in practice.
// Array as a functor
[1,2,3].map(n => n * 2); // [2,4,6]

// Promise behaves monad-like: chained then() auto-flattens nested promises
Promise.resolve(1).then(n => Promise.resolve(n + 1)).then(console.log); // 2
Real-world example Recognizing that chaining .then() on Promises already gives you monad-like flattening for free, without needing a library.

Common follow-ups: Do libraries like Ramda or fp-ts provide more formal functor/monad implementations?

Iterators & Generators

How do you avoid deeply nested, mutation-heavy code when updating nested state immutably?

Advanced
Use spread syntax (or structuredClone/immutable libraries like Immer) to create new objects/arrays at each level being changed, copying siblings shallowly while replacing only the path that changed — avoiding direct mutation of nested structures.
const state = { user: { profile: { age: 30 } } };
const updated = {
  ...state,
  user: {
    ...state.user,
    profile: { ...state.user.profile, age: 31 }
  }
};
Real-world example Updating a single deeply nested field in Redux or React state without mutating the original state object.

Common follow-ups: How does a library like Immer simplify this compared to manual spreading?

Destructuring Spread & Rest