Functional Programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. Key principles include:
A pure function always returns the same result for the same input and has no side effects. This makes the code more predictable, testable, and easier to understand.
Example of an impure function:
let discount = 0.1; // External state
function calculateDiscountedPrice(price) {
// Depends on the external state `discount`
return price - price * discount;
}
Example of a pure function:
function calculateDiscountedPrice(price, discount) {
// The result depends only on the arguments
return price - price * discount;
}
console.log(calculateDiscountedPrice(100, 0.1)); // 90
console.log(calculateDiscountedPrice(100, 0.1)); // 90 (always the same result)
Instead of modifying objects or arrays directly, FP encourages creating copies with the necessary changes. This helps avoid unintended side effects and simplifies tracking state changes.
Example with mutation:
const user = { name: 'Alice', age: 25 };
function celebrateBirthday(person) {
// Direct object modification (mutation)
person.age++;
return person;
}
celebrateBirthday(user);
console.log(user); // { name: 'Alice', age: 26 } (original object is modified)
Example with immutability:
const user = { name: 'Alice', age: 25 };
function celebrateBirthday(person) {
// Create a new object instead of modifying the old one
return {
...person,
age: person.age + 1
};
}
const updatedUser = celebrateBirthday(user);
console.log(user); // { name: 'Alice', age: 25 } (original object is untouched)
console.log(updatedUser); // { name: 'Alice', age: 26 } (a new object is returned)
Higher-order functions are functions that can take other functions as arguments or return them as a result. This is a powerful tool for creating abstractions.
Example:
Array methods like map
, filter
, and reduce
are classic examples of higher-order functions.
const numbers = [1, 2, 3, 4, 5];
// `filter` takes a predicate function as an argument
const evenNumbers = numbers.filter(function(n) {
return n % 2 === 0;
});
console.log(evenNumbers); // [2, 4]
// A function that returns another function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Composition allows you to build complex functions by combining simple ones. It’s like an assembly line: data passes through one function, its result becomes the input for the next, and so on.
Example:
Let’s say we need to take a string, make it uppercase, and add an exclamation mark.
const toUpperCase = (str) => str.toUpperCase();
const addExclamation = (str) => `${str}!`;
// Instead of nested calls: addExclamation(toUpperCase('hello'))
// you can use composition.
// A simple compose function
const compose = (f, g) => (x) => f(g(x));
const shout = compose(addExclamation, toUpperCase);
console.log(shout('hello world')); // 'HELLO WORLD!'
In real-world projects, composition utilities from libraries like Lodash or Ramda are often used, which allow combining more than two functions.
Applying these principles makes code more declarative (we describe what to do, not how), reliable, and easily scalable.