What are the principles of Functional Programming?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Hard
#FP #Theory

Quick Answer

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:

  1. Pure Functions: A function is pure if its return value depends only on its arguments, and it has no observable side effects (e.g., modifying global variables or logging to the console).
  2. Immutability: Data cannot be changed after it’s created. Instead of modifying existing data structures, new ones are created.
  3. First-Class Citizens: Functions can be assigned to variables, passed as arguments to other functions, and returned from them. Functions that accept or return other functions are called Higher-Order Functions.
  4. Function Composition: The process of combining multiple simple functions to create a more complex one. The result of one function is passed as the argument to the next.

Detailed Answer with Examples

1. Pure Functions

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)

2. Immutability

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)

3. Higher-Order Functions

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

4. Function Composition

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.