Functional Programming Concepts
Functional programming is a programming style where functions are treated as first-class citizens and emphasis is placed on pure functions, immutability, and composition. It focuses on writing predictable, reusable, and testable code.
In JavaScript, functions are first-class citizens, meaning they can be passed, returned, and stored like any other value.
Currying
Currying is a functional programming technique where a function with multiple arguments is transformed into a chain of nesting functions, each taking a single argument.
Instead of evaluating f(a, b, c) all at once, currying breaks it down into f(a)(b)(c)
// Curried function: Takes argument `a` and returns a new inner function
function multiply(a) {
// Inner function: Closes over `a` via closure and accepts argument `b`
return function(b) {
return a * b;
};
}
// Arrow function equivalent (shorthand):
// const multiply = (a) => (b) => a * b;
const double = multiply(2); // returns a new function: b => 2 * b
console.log(double(5)); // Outputs: 10
const triple = multiply(3); // returns a new function: b => 3 * b
console.log(triple(5)); // Outputs: 15
// Direct chaining invocation (when passing all arguments at once):
console.log(multiply(4)(5)); // Output: 20
Real-world Usage
// Curried base function
const applyDiscount = (rate) => (price) => price - price * rate;
// Step 1: Pre-configure specialized discount functions
const blackFridayDiscount = applyDiscount(0.30); // 30% off
const clearanceDiscount = applyDiscount(0.50); // 50% off
const cartPrices = [100, 200, 400];
// ✅ Direct: No wrapper needed because `blackFridayDiscount` expects just (price)
const discountedCart = cartPrices.map(blackFridayDiscount);
console.log(discountedCart); // [70, 140, 280]
Higher Order Function
A higher-order function is a function that takes another function as an argument or returns a function as a result.
// 1. Regular function (the "Callback"):
// Takes a string and returns a greeting string.
function greet(name) {
return "Hello, " + name;
}
// 2. Higher-Order Function (HOF):
// It qualifies as an HOF because it accepts another function as a parameter (`callback`).
function processUserInput(callback) {
let name = "Arjit";
// Invokes the callback function, passing `name` to it, and logs the result.
console.log(callback(name));
}
// 3. Execution:
// Pass `greet` by reference (WITHOUT parentheses `()`) into `processUserInput`.
processUserInput(greet); // Outputs: Hello, Arjit
Pure Functions
A function is called pure if it satisfies two core criteria:
- Deterministic: Given the same inputs, it will always return the exact same output.
- Zero Side Effects: It does not read from or mutate anything outside its own local scope (no modifying external variables, no API calls, no DOM updates, and no I/O operations).
// Output depends strictly on parameters a and b
// No external state is read or changed.
function add(a, b) {
return a + b;
}
add(2, 3); // Always 5
Pure functions are used for core business logic—like calculating prices, formatting API data, and validating forms - because they are 100% predictable, easy to test, and never accidentally break outside state.
Immutability
Functional programming avoids modifying existing data. Instead, it creates new data structures.
const list = [1, 2, 3];
// Modifies list directly
list.push(4);
// list remains unchanged
const newList = [...list, 4];
Array Methods
These methods are commonly used in functional programming because they do not mutate the original array and return new values.
- map() - Creates a new array by applying a function to each element of an existing array
const numbers = [1, 2, 3, 4];
const squared = numbers.map(num => num * num);
console.log(squared); // [1, 4, 9, 16]
- filter() - Creates a new array with only the elements that satisfy a given condition.
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
- reduce() - reduces an array to a single value by applying a function to each element.
const values = [10, 20, 30];
const total = values.reduce((acc, num) => acc + num, 0);
console.log(total); // 60
Composition and Piping
Both Composition and Piping combine small, single-purpose functions into a step-by-step assembly line: the output of one function becomes the input of the next.
The only difference is the direction data flows.
Composition (Right-to-Left)
Evaluates from the inside out (traditional math notation: $f(g(x))$).
const add2 = (x) => x + 2;
const multiplyBy3 = (x) => x * 3;
// Execution order: add2 runs FIRST, multiplyBy3 runs SECOND
const result = multiplyBy3(add2(5));
Note - The Problem: As you add more steps, reading inside-out gets messy: wrap(format(sanitize(trim(input))))
Piping (Modern Approach)
Piping solves the readability issue by running functions in natural reading order (left to right / top to bottom).
// A simple pipe utility using Array.reduce
const pipe = (...fns) => (initialValue) =>
fns.reduce((value, fn) => fn(value), initialValue);
// Single-purpose steps
const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const toKebabCase = (str) => str.replaceAll(" ", "-");
const addPrefix = (str) => `post-${str}`;
// ✅ Clear, readable data pipeline
const createSlug = pipe(
trim,
toLowerCase,
toKebabCase,
addPrefix
);
createSlug(" Hello World from Functional JS ");
// Output: "post-hello-world-from-functional-js"