Key Properties
Why purity matters
function add(a, b) {
return a + b;
}
Rules
function add(a, b) {
return a + b;
}
Rules
PURE
let taxRate = 0.14;
function calculatePrice(price) {
return price + price * taxRate;
}
Rules
let taxRate = 0.14;
function calculatePrice(price) {
return price + price * taxRate;
}
Rules
IMPURE
Why?
function addItem(cart, item) {
cart.push(item);
return cart;
}
Rules
function addItem(cart, item) {
cart.push(item);
return cart;
}
Rules
IMPURE
Why?
Mutates the input array
The same input reference can give different results
Breaks immutability
function formatUser(user) {
return {
...user,
lastLogin: new Date().toISOString()
};
}
Rules
function formatUser(user) {
return {
...user,
lastLogin: new Date().toISOString()
};
}
Rules
IMPURE
Why?
Uses Date() (time dependency)
Same input → different output every call
Breaks determinism
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
Rules
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2 ❌
Rules
IMPURE
Why?
Returned function produces different output on each call
Not referentially transparent
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2
A language has first-class functions when functions are treated like values.
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2
A language has first-class functions when functions are treated like values.
A language has first-class functions when functions are treated like values.
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2
Long story short first-class function
is a Language Feature
A higher-order function is any function that does one of these:
function createCounter(start, triple) {
let count = start;
return () => {
count++;
return triple(count);
};
}
const counter = createCounter(0);
counter(); // 3
counter(); // 6
A higher-order function is any function that does one of these:
function createCounter(start, triple) {
let count = start;
return () => {
count++;
return triple(count);
};
}
const counter = createCounter(0);
counter(); // 3
counter(); // 6
HOFs exist because functions are first-class
VS
function resolveCartTotal(cartItems) {
let total = 0;
for (let i = 0; i < cartItems.length; i++) {
const item = cartItems[i];
if (!item.isGift) {
total += item.price * item.quantity;
}
}
return total;
}
function resolveCartTotal(cartItems) {
return cartItems
.filter((item) => !item.isGift)
.map((item) => item.price * item.quantity)
.reduce((sum, price) => sum + price, 0);
}
How to do it, step by step
What I want
Each recursive call returns a new value instead of modifying variables
keeping functions pure and referentially transparent.
const imperativeFactorial = (n) => {
let product = 1;
while (n > 0) {
product *= n;
n--;
}
return product;
};recursive
const recursiveFactorial = (n) => {
if (n === 0) return 1;
return n * recursiveFactorial(n - 1);
};Each recursive call returns a new value instead of modifying variables
keeping functions pure and referentially transparent.
const imperativeFactorial = (n) => {
let product = 1;
while (n > 0) {
product *= n;
n--;
}
return product;
};recursive
const recursiveFactorial = (n) => {
if (n === 0) return 1;
return n * recursiveFactorial(n - 1);
};‼️Warning
Too much recursion could lead to a stack overflow
Use Tail Call Optimization (TCO)
Solution
function fetchData(baseUrl, endpoint, options = {}) {
return fetch(`${baseUrl}${endpoint}`, {
method: "GET",
headers: {
Authorization: "Bearer TOKEN",
"Content-Type": "application/json",
},
...options,
}).then((res) => res.json());
}
Currying transforms a function that takes multiple arguments into a chain of functions that take one argument at a time.
const createFetchClient = (baseUrl) => (defaultOptions) => (endpoint) =>
fetch(`${baseUrl}${endpoint}`, defaultOptions).then((res) => res.json());
const apiClient = createFetchClient("https://api.example.com")({
headers: {
Authorization: "Bearer TOKEN",
"Content-Type": "application/json",
},
});
apiClient("/users");
apiClient("/products");
apiClient("/orders");
Currying transforms a function that takes multiple arguments into a chain of functions that take one argument at a time.
Functional programming focuses on small and pure functions.
Using composition, we can use small functions to construct complex ones
const trim = str => str.trim();
const toLower = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, '');
const normalizeInput = pipe(
trim,
toLower,
removeSpaces
);
normalizeInput(" He LLo "); // "hello"
const compose =
(...fns) =>
(x) =>
fns.reduceRight((value, fn) => fn(value), x);
const double = (x) => x * 2;
const addOne = (x) => x + 1;
const process = compose(addOne, double);
process(3); // 7
Compose and pipe are HOF.
Used for building complex functions by connecting smaller ones
Compose
Pipe
const pipe =
(...fns) =>
(x) =>
fns.reduce((value, fn) => fn(value), x);
const double = (x) => x * 2;
const addOne = (x) => x + 1;
const process = pipe(double, addOne);
process(3); // 7
VS
Left ⬅️ Right
Left ➡️ Right
Don't forget, use FP ideas where they fit
DON'T FORCE