Functional Programming: Currying, Closures & Handling Failure
Currying, Partial Application & Closures
// Currying: transform a multi-arg function into a chain of
// single-argument functions
const add = a => b => a + b;
const add5 = add(5); // a specialized function, remembers a=5 via closure
add5(3); // 8
// The CLOSURE is what makes this work -- the returned inner function
// retains access to 'a' even after the outer add() call has finished
function multiplier(n) {
return function (x) {
return x * n; // 'n' is closed over
};
}
const triple = multiplier(3);
triple(7); // 21
// Partial application: fix SOME arguments upfront, any number remain
// (distinct from currying's strict one-argument-per-call chain)
function add3(a, b, c) { return a + b + c; }
const addTo5 = add3.bind(null, 5);
addTo5(2, 3); // 10 -- two args at once, not curried one-at-a-timeRecursion Instead of Mutable Loops
// Iteration expressed through recursive calls, no mutating
// loop counter/accumulator variable
function sum([head, ...tail]) {
if (head === undefined) return 0;
return head + sum(tail);
}
sum([1, 2, 3, 4]); // 10
function factorial(n, acc = 1) {
return n <= 1 ? acc : factorial(n - 1, n * acc); // tail-call formResult/Either: Handling Failure Without Exceptions
// A thrown exception is an invisible control-flow path not
// reflected in a function's signature -- easy to forget to catch.
// A Result type makes "this can fail" part of the type contract.
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
function parseAge(input: string): Result<number, string> {
const n = Number(input);
if (Number.isNaN(n) || n < 0) {
return { ok: false, error: `Invalid age: ${input}` };
}
return { ok: true, value: n };
}
const result = parseAge('30');
if (result.ok) {
console.log(result.value); // caller MUST check .ok before accessing .value
} else {
console.error(result.error);
}
// This is the same shape as a Promise (which resolves or rejects)
// or Maybe/Option (present or absent) -- 'monad-like' wrappers that
// provide a consistent .map()/.flatMap() way to chain operations
// without manually unwrapping and re-checking at every step.Why This Makes Testing Easier
A pure function needs no mocking of a database, network, or clock -- call it with sample inputs, assert on the output.
No cleanup of mutated global state needed between tests -- there's nothing shared to reset.
Referential transparency means a function call can be replaced with its result in reasoning about behavior -- makes tests (and code review) easier to follow.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free