Skip to main content

Module 22: Functional Programming

Functional Programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions, avoiding state changes and mutable data.


1. Core Concepts

1.1 Pure Functions

// ❌ Impure: depends on external state
let total = 0;
function addToTotal(value) {
total += value;
return total;
}

// ✅ Pure: same input always gives same output
function add(a, b) {
return a + b;
}

console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5 (always the same)

Characteristics of Pure Functions:

  • No side effects
  • Same input → same output
  • No external dependencies
  • Predictable and testable

1.2 Immutability

// ❌ Mutable
const user = { name: 'John', age: 30 };
user.age = 31; // Mutation!

// ✅ Immutable
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31 }; // New object

// Arrays
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // New array

1.3 First-Class Functions

// Functions as values
const greet = function(name) {
return `Hello, ${name}`;
};

// Functions as arguments
function executeFunction(fn, value) {
return fn(value);
}

console.log(executeFunction(greet, 'John')); // "Hello, John"

// Functions as return values
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}

const double = createMultiplier(2);
console.log(double(5)); // 10

2. Higher-Order Functions

2.1 map()

const numbers = [1, 2, 3, 4, 5];

// Transform array
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// Extract properties
const users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 }
];

const names = users.map(user => user.name);
console.log(names); // ['John', 'Jane']

2.2 filter()

const numbers = [1, 2, 3, 4, 5, 6];

// Filter even numbers
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6]

// Filter objects
const users = [
{ name: 'John', age: 30, active: true },
{ name: 'Jane', age: 25, active: false },
{ name: 'Bob', age: 35, active: true }
];

const activeUsers = users.filter(user => user.active);
console.log(activeUsers); // [John, Bob]

2.3 reduce()

const numbers = [1, 2, 3, 4, 5];

// Sum
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// Product
const product = numbers.reduce((acc, n) => acc * n, 1);
console.log(product); // 120

// Group by property
const users = [
{ name: 'John', role: 'admin' },
{ name: 'Jane', role: 'user' },
{ name: 'Bob', role: 'admin' }
];

const grouped = users.reduce((acc, user) => {
const role = user.role;
acc[role] = acc[role] || [];
acc[role].push(user);
return acc;
}, {});

console.log(grouped);
// { admin: [{John}, {Bob}], user: [{Jane}] }

3. Function Composition

3.1 Basic Composition

// Simple composition
const add = x => x + 1;
const multiply = x => x * 2;

const addThenMultiply = x => multiply(add(x));
console.log(addThenMultiply(5)); // (5 + 1) * 2 = 12

// Compose function
const compose = (...fns) => x =>
fns.reduceRight((acc, fn) => fn(acc), x);

const pipe = (...fns) => x =>
fns.reduce((acc, fn) => fn(acc), x);

// Usage
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const composed = compose(square, double, addOne);
console.log(composed(5)); // square(double(addOne(5))) = 144

const piped = pipe(addOne, double, square);
console.log(piped(5)); // square(double(addOne(5))) = 144

3.2 Real-World Example

// Processing user data
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const capitalize = str => str[0].toUpperCase() + str.slice(1);

const processName = pipe(trim, toLowerCase, capitalize);

console.log(processName(' jOHN ')); // "John"

// Price calculation
const addTax = price => price * 1.2;
const addShipping = price => price + 5;
const applyDiscount = discount => price => price * (1 - discount);

const calculateFinalPrice = pipe(
addTax,
addShipping,
applyDiscount(0.1)
);

console.log(calculateFinalPrice(100)); // 113

4. Currying

4.1 Manual Currying

// Normal function
function add(a, b, c) {
return a + b + c;
}

// Curried function
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}

console.log(curriedAdd(1)(2)(3)); // 6

// Arrow function syntax
const curriedAdd = a => b => c => a + b + c;
console.log(curriedAdd(1)(2)(3)); // 6

4.2 Curry Utility

function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}

// Usage
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6

4.3 Practical Applications

// Configuration
const multiply = curry((multiplier, value) => value * multiplier);

const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// URL builder
const buildURL = curry((protocol, domain, path) =>
`${protocol}://${domain}/${path}`
);

const buildHTTPS = buildURL('https');
const buildAPI = buildHTTPS('api.example.com');

console.log(buildAPI('users')); // "https://api.example.com/users"
console.log(buildAPI('products')); // "https://api.example.com/products"

5. Partial Application

5.1 Partial Function

function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}

// Usage
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}

const sayHello = partial(greet, 'Hello');
const sayGoodbye = partial(greet, 'Goodbye');

console.log(sayHello('John')); // "Hello, John!"
console.log(sayGoodbye('John')); // "Goodbye, John!"

5.2 bind() Method

const multiply = (a, b) => a * b;

const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

6. Recursion

6.1 Basic Recursion

// Factorial
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

// Fibonacci
function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(7)); // 13

6.2 Tail Recursion

// Tail-recursive factorial
function factorial(n, acc = 1) {
if (n === 0) {
return acc;
}
return factorial(n - 1, n * acc);
}

console.log(factorial(5)); // 120

// Tail-recursive sum
function sum(arr, acc = 0) {
if (arr.length === 0) {
return acc;
}
const [head, ...tail] = arr;
return sum(tail, acc + head);
}

console.log(sum([1, 2, 3, 4, 5])); // 15

6.3 Recursion with Arrays

// Flatten nested arrays
function flatten(arr) {
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
return acc.concat(flatten(item));
}
return acc.concat(item);
}, []);
}

console.log(flatten([1, [2, [3, [4]], 5]])); // [1, 2, 3, 4, 5]

// Deep clone
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}

return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, deepClone(value)])
);
}

7. Declarative vs Imperative

7.1 Imperative (How)

// Imperative: step-by-step instructions
const numbers = [1, 2, 3, 4, 5];
let doubled = [];

for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}

console.log(doubled); // [2, 4, 6, 8, 10]

7.2 Declarative (What)

// Declarative: describe the result
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);

console.log(doubled); // [2, 4, 6, 8, 10]

8. Practical Examples

8.1 Data Transformation Pipeline

const users = [
{ name: 'John', age: 30, active: true, score: 85 },
{ name: 'Jane', age: 25, active: false, score: 92 },
{ name: 'Bob', age: 35, active: true, score: 78 },
{ name: 'Alice', age: 28, active: true, score: 95 }
];

// Functional pipeline
const result = users
.filter(user => user.active)
.filter(user => user.score >= 80)
.map(user => ({
name: user.name,
grade: user.score >= 90 ? 'A' : 'B'
}))
.sort((a, b) => b.grade.localeCompare(a.grade));

console.log(result);
// [{ name: 'Alice', grade: 'A' }, { name: 'John', grade: 'B' }]

8.2 Validation System

// Validators as functions
const validators = {
required: value => value !== '' && value != null,
minLength: min => value => value.length >= min,
maxLength: max => value => value.length <= max,
email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
numeric: value => !isNaN(value)
};

// Compose validators
function validate(value, ...validatorFns) {
return validatorFns.every(fn => fn(value));
}

// Usage
const email = 'john@example.com';
const isValidEmail = validate(
email,
validators.required,
validators.email
);

console.log(isValidEmail); // true

// Reusable validators
const validateUsername = value => validate(
value,
validators.required,
validators.minLength(3),
validators.maxLength(20)
);

console.log(validateUsername('john')); // true
console.log(validateUsername('jo')); // false

8.3 State Management

// Immutable state updates
const initialState = {
user: null,
posts: [],
loading: false
};

// State updaters
const setUser = user => state => ({
...state,
user
});

const addPost = post => state => ({
...state,
posts: [...state.posts, post]
});

const setLoading = loading => state => ({
...state,
loading
});

// Apply updates
let state = initialState;
state = setUser({ id: 1, name: 'John' })(state);
state = addPost({ id: 1, title: 'First Post' })(state);
state = setLoading(false)(state);

console.log(state);

9. Functional Utilities

9.1 Utility Functions

// Identity
const identity = x => x;

// Constant
const constant = x => () => x;

// Not
const not = fn => (...args) => !fn(...args);

// Once
const once = fn => {
let called = false;
let result;
return (...args) => {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
};

// Usage
const initialize = once(() => {
console.log('Initialized!');
return { initialized: true };
});

initialize(); // "Initialized!"
initialize(); // (nothing logged, returns cached result)

9.2 Memoization

function memoize(fn) {
const cache = new Map();

return function(...args) {
const key = JSON.stringify(args);

if (cache.has(key)) {
return cache.get(key);
}

const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}

// Expensive calculation
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});

console.time('fib');
console.log(fibonacci(40)); // Fast with memoization
console.timeEnd('fib');

10. Best Practices

10.1 Avoid Side Effects

// ❌ Side effects
let counter = 0;
function increment() {
counter++; // Modifies external state
console.log(counter); // Side effect
}

// ✅ Pure function
function increment(counter) {
return counter + 1; // Returns new value
}

let counter = 0;
counter = increment(counter);

10.2 Use Immutable Operations

// ❌ Mutation
const array = [1, 2, 3];
array.push(4); // Mutates original

// ✅ Immutable
const array = [1, 2, 3];
const newArray = [...array, 4]; // New array

10.3 Prefer map/filter/reduce Over Loops

// ❌ Imperative loop
let result = [];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
result.push(numbers[i] * 2);
}
}

// ✅ Functional
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2);

Summary

In this module, you learned:

  • ✅ Core FP concepts: pure functions, immutability, first-class functions
  • ✅ Higher-order functions: map, filter, reduce
  • ✅ Function composition and piping
  • ✅ Currying and partial application
  • ✅ Recursion and tail recursion
  • ✅ Declarative vs imperative programming
  • ✅ Practical FP patterns and utilities
  • ✅ Best practices for functional JavaScript
Next Steps

In Module 23, you'll learn about Array Advanced Methods for data manipulation.


Practice Exercises

  1. Create a compose/pipe utility from scratch
  2. Build a validation library with composable validators
  3. Implement a curry function
  4. Create a state management system using FP principles
  5. Build a data transformation pipeline
  6. Implement memoization for expensive functions
  7. Create recursive algorithms (factorial, fibonacci, tree traversal)
  8. Build a functional programming utility library

Additional Resources