Functional Programming
Thinking in Functions
Pure Functions
Key Properties
- Same Input ➡️ Same Output (Deterministic)
- Has no side effects
Why purity matters
- Predictability
- Testability
- Referential transparency (replace function with return value)
Pure or Poisoned?
function add(a, b) {
return a + b;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
Pure or Poisoned?
function add(a, b) {
return a + b;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
PURE
Pure or Poisoned?
let taxRate = 0.14;
function calculatePrice(price) {
return price + price * taxRate;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
Pure or Poisoned?
let taxRate = 0.14;
function calculatePrice(price) {
return price + price * taxRate;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
IMPURE
Why?
- Depends on an external variable.
Pure or Poisoned?
function addItem(cart, item) {
cart.push(item);
return cart;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
Pure or Poisoned?
function addItem(cart, item) {
cart.push(item);
return cart;
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
IMPURE
Why?
-
Mutates the input array
-
The same input reference can give different results
-
Breaks immutability
Pure or Poisoned?
function formatUser(user) {
return {
...user,
lastLogin: new Date().toISOString()
};
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
Pure or Poisoned?
function formatUser(user) {
return {
...user,
lastLogin: new Date().toISOString()
};
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
IMPURE
Why?
-
Uses
Date()(time dependency) -
Same input → different output every call
-
Breaks determinism
Pure or Poisoned?
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
Pure or Poisoned?
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2 ❌
Rules
- Same input ➡️ Same Output
- No side effects
- referentially transparent
IMPURE
Why?
-
Returned function produces different output on each call
-
Not referentially transparent
First Class Function
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.
First Class Function
function createCounter(start) {
let count = start;
return () => {
count++;
return count;
};
}
const counter = createCounter(0);
counter(); // 1
counter(); // 2
- store them in variables
- pass them to other functions
- return them from functions
- put them in arrays or objects
A language has first-class functions when functions are treated like values.
First Class Function
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
- store them in variables
- pass them to other functions
- return them from functions
- put them in arrays or objects
Higher-order functions (HOFs)
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
- takes another function as an argument
- returns a function
Higher-order functions (HOFs)
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
- takes another function as an argument
- returns a function
- First-class functions → a language feature
-
Higher-order functions → a pattern that becomes possible because of that feature
Imperative
Declarative
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
Recursion
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);
};Recursion
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
Currying
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.
Currying
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.
- Currying is powerful when it improves reuse and composition.
- Currying is harmful when it hurts readability and clarity.
Composition
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"
Compose vs Pipe
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
Think Functions
- treat data as read-only
- isolate and minimize side effects.
- declarative, FP cares about values, not steps
- Break Problems Into Tiny Functions
- Think in Pipelines
Don't forget, use FP ideas where they fit
DON'T FORCE
Functional Programming
By Mohamed Dewidar
Functional Programming
- 11