Functional Programming
Apply functional programming principles pure functions, immutability, currying, function composition, and functors to write predictable, testable JavaScript.
Overview
Key Points
- •Pure functions: same input always produces same output; no side effects (no mutation, I/O, or global state)
- •Immutability: use Object.freeze, spread ({...obj}), and Array methods that return new arrays instead of mutating
- •Currying transforms f(a, b) into f(a)(b) enables partial application and reusable specialised functions
- •Partial application fixes some arguments upfront; the resulting function takes the remaining arguments later
- •Function composition (compose/pipe) chains functions left-to-right or right-to-left into a pipeline
- •A functor is anything with a map method that preserves structure Array, Maybe, and Promise are all functors
- •Transducers compose transformations without building intermediate arrays, processing data in a single pass
- •Point-free style: define functions as compositions without referencing the data argument explicitly
Examples
Currying and partial application
// Manual curry
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...more) {
return curried.apply(this, args.concat(more));
};
};
}
const add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2)(3)); // 6
console.log(add(1)(2, 3)); // 6
const add10 = add(10); // partial application
const add10and5 = add10(5);
console.log(add10and5(2)); // 17
// Practical use: curried filter predicate
const isGreaterThan = curry((threshold, value) => value > threshold);
const isAdult = isGreaterThan(17);
const ages = [14, 22, 16, 25, 13, 18];
console.log(ages.filter(isAdult)); // [22, 25, 18]Curried functions are composable you build specialised functions from general ones by partial application.
Function composition and pipe
// Right-to-left (mathematical) composition
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
// Left-to-right (pipeline) composition
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const toStr = x => `Result: ${x}`;
// compose applies right-to-left: square(addOne(double(3)))
const transform1 = compose(toStr, square, addOne, double);
console.log(transform1(3)); // Result: 49 (3*2=6, 6+1=7, 7^2=49)
// pipe applies left-to-right: same functions, same result
const transform2 = pipe(double, addOne, square, toStr);
console.log(transform2(3)); // Result: 49
// Real-world use: data pipeline
const processUsers = pipe(
users => users.filter(u => u.active),
users => users.map(u => ({ ...u, name: u.name.trim().toUpperCase() })),
users => users.sort((a, b) => a.name.localeCompare(b.name)),
);
const users = [
{ name: ' alice ', active: true },
{ name: 'BOB', active: false },
{ name: 'charlie ', active: true },
];
console.log(processUsers(users));
// [{ name: 'ALICE', active: true }, { name: 'CHARLIE', active: true }]pipe reads in execution order (top-to-bottom) which most developers find more intuitive than compose.
Maybe functor safe null handling without if-chains
class Maybe {
constructor(value) { this._value = value; }
static of(value) { return new Maybe(value); }
static empty() { return new Maybe(null); }
isNothing() { return this._value == null; }
map(fn) {
return this.isNothing() ? Maybe.empty() : Maybe.of(fn(this._value));
}
getOrElse(defaultValue) {
return this.isNothing() ? defaultValue : this._value;
}
toString() {
return this.isNothing() ? 'Nothing' : `Just(${this._value})`;
}
}
// Safe property access chain
const getCity = user =>
Maybe.of(user)
.map(u => u.address)
.map(a => a.city)
.map(c => c.toUpperCase())
.getOrElse('UNKNOWN');
console.log(getCity({ address: { city: 'London' } })); // LONDON
console.log(getCity({ address: null })); // UNKNOWN
console.log(getCity(null)); // UNKNOWN
console.log(getCity({})); // UNKNOWNMaybe is a functor because it implements map that preserves the Maybe structure. It safely propagates null through a chain without if/else at every step.
Common Mistakes
- Calling array methods that mutate (sort, reverse, splice, push) in a "functional" pipeline always copy first: [...arr].sort()
- Writing impure functions that read from or write to variables outside their scope, making them untestable in isolation
- Over-abstracting with point-free style when a simple named function would be clearer FP is a tool, not a dogma
- Accidentally sharing mutable objects by spreading only shallowly nested objects are still the same reference after {...obj}
Note
Start applying FP incrementally: make your utility functions pure first, then introduce currying where you find yourself creating many similar filter/map callbacks. You do not need to adopt full monadic style to benefit from FP even 10% FP discipline (pure utils, immutable updates) pays dividends in testability.
Exercises
Write JavaScript code to solve each exercise. Run it and check your output.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4