Functional Programming Concepts Cheat Sheet
Pure functions, immutability, higher-order functions, function composition, and currying — the core ideas behind functional programming, explained with runnable JavaScript examples.
Pure Functions & Immutability
Contrasting impure, stateful code with pure functions and immutable updates.
// Impure: relies on and mutates external statelet total = 0;function addImpure(x) { total += x; return total; }// Pure: same input always produces same output, no side effectsfunction add(a, b) { return a + b; }// Immutable update instead of mutationconst original = { name: "Ada", age: 30 };const updated = { ...original, age: 31 }; // original is untouchedconst list = [1, 2, 3];const appended = [...list, 4]; // new array, list unchanged
Higher-Order Functions
Using map, filter, and reduce instead of manual loops.
const nums = [1, 2, 3, 4, 5];const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]const evens = nums.filter(n => n % 2 === 0); // [2, 4]const sum = nums.reduce((acc, n) => acc + n, 0); // 15// A function that returns a functionconst multiplyBy = factor => n => n * factor;const triple = multiplyBy(3);triple(7); // 21
Composition & Currying
Combining small functions and partially applying arguments.
// compose: right-to-left function compositionconst compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);const addOne = n => n + 1;const double = n => n * 2;const addThenDouble = compose(double, addOne);addThenDouble(3); // (3 + 1) * 2 = 8// Currying: transform f(a, b, c) into f(a)(b)(c)const curry = fn => (...args) => args.length >= fn.length ? fn(...args) : (...more) => curry(fn)(...args, ...more);const add3 = curry((a, b, c) => a + b + c);add3(1)(2)(3); // 6add3(1, 2)(3); // 6
Core FP Concepts
Key vocabulary used throughout functional programming.
- Pure Function- Given the same input, always returns the same output and produces no observable side effects
- Immutability- Data cannot be changed after creation; updates create new copies instead of mutating in place
- First-Class Functions- Functions can be assigned to variables, passed as arguments, and returned from other functions
- Higher-Order Function- A function that takes one or more functions as arguments and/or returns a function
- Referential Transparency- An expression can be replaced with its resulting value without changing the program's behavior
- Function Composition- Combining simple functions to build more complex ones, e.g. compose(f, g)(x) equals f(g(x))
- Currying- Transforming a function of N arguments into a chain of N functions that each take one argument
- Closures- A function that retains access to variables from its enclosing scope even after that scope has returned
- Recursion- Solving a problem by having a function call itself on a smaller subproblem, often replacing loops
- Monad- A pattern that wraps a value and defines how to chain operations over it, e.g. Promise, Maybe/Option
Functors & Monads (Maybe)
Implementing a minimal Maybe monad that chains computations and short-circuits on null/undefined.
// A minimal Maybe monad: chains computations that might failconst Maybe = value => ({ map: fn => (value === null || value === undefined) ? Maybe(null) : Maybe(fn(value)), chain: fn => (value === null || value === undefined) ? Maybe(null) : fn(value), getOrElse: fallback => value ?? fallback});const safeDivide = (a, b) => b === 0 ? Maybe(null) : Maybe(a / b);const result = Maybe(20) .map(n => n * 2) .chain(n => safeDivide(n, 4)) .map(n => n + 1) .getOrElse(0);// 11 -- each step short-circuits automatically once a null/undefined appears
Point-Free Style & pipe
Composing functions left-to-right with pipe, written without naming intermediate arguments.
// pipe: left-to-right composition, opposite of composeconst pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);// Point-free: functions defined without naming their argumentsconst trim = s => s.trim();const toLower = s => s.toLowerCase();const split = sep => s => s.split(sep);const words = pipe(trim, toLower, split(" "));words(" Hello FUNCTIONAL World "); // ["hello", "functional", "world"]// Pointful equivalent for comparison -- same behavior, names the argumentfunction wordsPointful(s) { return s.trim().toLowerCase().split(" ");}
Lazy Evaluation with Generators
Building infinite, lazily-evaluated sequences with generator functions instead of materializing arrays.
// Generators give lazy, infinite sequences without building arrays in memoryfunction* naturals() { let n = 1; while (true) yield n++;}function* takeFn(iterable, count) { let i = 0; for (const value of iterable) { if (i++ >= count) return; yield value; }}function* mapGen(iterable, fn) { for (const value of iterable) yield fn(value);}const firstFiveSquares = [...takeFn(mapGen(naturals(), n => n * n), 5)];// [1, 4, 9, 16, 25] -- nothing is computed until the spread consumes it
Persistent Data Structures
Structural sharing lets immutable updates reuse untouched parts of a structure instead of deep-copying it.
// Structural sharing: updates reuse untouched nodes instead of copying everythingclass PersistentList { constructor(head = null, tail = null, size = 0) { this.head = head; this.tail = tail; this.size = size; } push(value) { // O(1) -- new node points at the old list; the old list is untouched return new PersistentList(value, this, this.size + 1); } *[Symbol.iterator]() { let node = this; while (node && node.size > 0) { yield node.head; node = node.tail; } }}const empty = new PersistentList();const a = empty.push(1);const b = a.push(2); // b shares the node holding 1 with aconst c = a.push(3); // c also shares that same node -- a is never mutated[...b]; // [2, 1][...c]; // [3, 1][...a]; // [1] -- unaffected by b and c
Advanced FP Vocabulary
Terms that come up once you move past basic map/filter/reduce into typed functional design.
- Functor- A type that implements map, applying a function inside a context (Array, Maybe, Promise) without unwrapping it
- Applicative- A functor that lets you apply a wrapped function to a wrapped value, typically via an ap() operation
- Monad- An applicative that adds chain/flatMap, letting one context-producing function feed cleanly into the next
- Monad Laws- Left identity, right identity, and associativity -- the algebraic rules chain() must obey to compose predictably
- Algebraic Data Type (ADT)- A type built from sums (tagged unions) and products (records), typically consumed via pattern matching
- Lens- A composable getter/setter pair used to read and immutably update deeply nested data
- Transducer- A composable, allocation-free transformation that fuses map/filter/reduce steps into a single pass
- Tail Call Optimization (TCO)- Reusing the current stack frame for a recursive call in tail position instead of growing the call stack
Favor .map/.filter/.reduce chains over manual for-loops with mutable accumulators — they make intent explicit and are easier to test in isolation, but stop chaining once a pipeline gets so long that debugging intermediate values becomes painful.