Functional Programming in JavaScript: From A to Z
Functional programming (FP) is a paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In the context of JavaScript (Node.js), FP becomes a powerful tool for creating predictable, testable, and scalable code. In this article, we will break down key FP concepts from the basics to advanced techniques, reinforcing theory with practical examples.
Fundamentals: Pure Functions and Immutability
At the core of FP lie two principles: pure functions and immutability.
A pure function always returns the same result for the same arguments and has no side effects (it does not modify external variables, log to the console, or make API requests).
// Pure functionconst add = (a, b) => a + b;
// Impure function (side effect — modifying an external variable)let counter = 0;const increment = () => counter++;Immutability means that data is not modified directly; instead, new copies with the desired changes are created. In JavaScript, methods that return new objects/arrays are used for this purpose.
// Immutable object updateconst user = { name: 'Alice', age: 30 };const updatedUser = { ...user, age: 31 }; // Creating a new object
// Immutable array updateconst numbers = [1, 2, 3];const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]Closures and Higher-Order Functions
A closure is a function that "remembers" its lexical environment even after the outer function has finished executing. This is the foundation for many FP patterns.
const createMultiplier = (factor) => { // Inner function — closure return (number) => number * factor;};
const double = createMultiplier(2);console.log(double(5)); // 10Higher-order functions are functions that take other functions as arguments or return them. Classic examples are map, filter, and reduce.
const numbers = [1, 2, 3, 4, 5];
// map — transforms each elementconst doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter — selects elements based on a conditionconst even = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce — reduces an array to a single valueconst sum = numbers.reduce((acc, n) => acc + n, 0); // 15Function Composition and Currying
Function composition is combining several functions into one, where the result of one function is passed as input to the next. This allows building complex operations from simple, reusable building blocks.
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const trim = (str) => str.trim();const toLowerCase = (str) => str.toLowerCase();const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const processName = compose(capitalize, toLowerCase, trim);console.log(processName(' JOHN DOE ')); // 'John doe'Currying is transforming a function with multiple arguments into a sequence of functions, each taking a single argument. This simplifies partial application and composition.
// Regular functionconst multiply = (a, b, c) => a * b * c;
// Curried versionconst curriedMultiply = (a) => (b) => (c) => a * b * c;
console.log(curriedMultiply(2)(3)(4)); // 24
// Partial applicationconst multiplyBy2 = curriedMultiply(2);const multiplyBy2And3 = multiplyBy2(3);console.log(multiplyBy2And3(5)); // 30