A pure function — is a function that:
// Pure function
function add(a, b) {
return a + b; // Always the same result for the same a and b
}
// Impure function
let counter = 0;
function increment() {
return ++counter; // Depends on external variable
}
A Pure Function — is a fundamental concept of functional programming that defines a function as a “mathematical” operation. Such functions are the foundation of predictable and reliable code.
// Mathematical operations
function multiply(a, b) {
return a * b;
}
// String operations
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
// Array operations (without mutation)
function filterEvenNumbers(numbers) {
return numbers.filter(num => num % 2 === 0);
}
// Object operations (without mutation)
function updateUserAge(user, newAge) {
return { ...user, age: newAge };
}
// Complex calculations
function calculateTax(price, taxRate) {
return price * (taxRate / 100);
}
// Depends on current time
function getCurrentTimestamp() {
return Date.now(); // Each call gives a different result
}
// Depends on external variable
let discount = 0.1;
function applyDiscount(price) {
return price * (1 - discount); // Result depends on external variable
}
// Uses random numbers
function generateRandomId() {
return Math.random().toString(36); // Always different result
}
// Depends on DOM
function getElementWidth(elementId) {
return document.getElementById(elementId).offsetWidth;
}
Side effects — are any changes to program state or interaction with the external world:
// Returns new array without modifying the original
function addItemToArray(array, item) {
return [...array, item];
}
// Returns new object without modifying the original
function updateUserProfile(user, updates) {
return {
...user,
...updates,
updatedAt: new Date().toISOString()
};
}
// Calculations without changing external state
function calculateMonthlyPayment(principal, rate, months) {
const monthlyRate = rate / 12 / 100;
const payment = principal *
(monthlyRate * Math.pow(1 + monthlyRate, months)) /
(Math.pow(1 + monthlyRate, months) - 1);
return Math.round(payment * 100) / 100;
}
// Modifies global variable
let totalSales = 0;
function addSale(amount) {
totalSales += amount; // Side effect!
return totalSales;
}
// Modifies passed object
function updateUser(user, newData) {
user.name = newData.name; // Mutation!
user.updatedAt = Date.now();
return user;
}
// Console output
function debugCalculation(a, b) {
const result = a + b;
console.log(`${a} + ${b} = ${result}`); // Side effect!
return result;
}
// Modifies DOM
function updateCounter(value) {
document.getElementById('counter').textContent = value; // Side effect!
return value;
}
// Pure function - always predictable result
function formatPrice(price, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(price);
}
// Can be confident in the result
console.log(formatPrice(1000)); // "$1,000.00"
console.log(formatPrice(1000)); // "$1,000.00" - always the same
// Pure function is easy to test
function calculateDiscount(price, discountPercent) {
if (discountPercent < 0 || discountPercent > 100) {
throw new Error('Discount must be between 0 and 100%');
}
return price * (discountPercent / 100);
}
// Simple tests
function testCalculateDiscount() {
console.assert(calculateDiscount(1000, 10) === 100);
console.assert(calculateDiscount(500, 20) === 100);
console.assert(calculateDiscount(0, 50) === 0);
try {
calculateDiscount(1000, -5);
console.assert(false, 'Should throw an error');
} catch (e) {
console.assert(e.message.includes('Discount must be'));
}
}
// Pure functions can be memoized
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 pure function
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Memoized version
const memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(40)); // Calculated
console.log(memoizedFibonacci(40)); // Retrieved from cache
// Pure functions are safe for parallel execution
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Pure function for processing
function processNumber(num) {
return num * num + Math.sqrt(num);
}
// Can safely use in Promise.all
const promises = numbers.map(num =>
Promise.resolve(processNumber(num))
);
Promise.all(promises).then(results => {
console.log(results); // Result is predictable
});
// Pure validation functions
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function isValidPassword(password) {
return password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password);
}
function validateUser(userData) {
const errors = [];
if (!userData.email || !isValidEmail(userData.email)) {
errors.push('Invalid email');
}
if (!userData.password || !isValidPassword(userData.password)) {
errors.push('Password must contain at least 8 characters, including uppercase and lowercase letters, digits');
}
return {
isValid: errors.length === 0,
errors
};
}
// Pure functions for data processing
function normalizeUser(rawUser) {
return {
id: rawUser.id,
name: rawUser.full_name?.trim() || '',
email: rawUser.email?.toLowerCase() || '',
age: parseInt(rawUser.age) || 0,
isActive: rawUser.status === 'active'
};
}
function groupUsersByAge(users) {
return users.reduce((groups, user) => {
const ageGroup = Math.floor(user.age / 10) * 10;
const groupKey = `${ageGroup}-${ageGroup + 9}`;
if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(user);
return groups;
}, {});
}
function calculateUserStats(users) {
return {
total: users.length,
active: users.filter(user => user.isActive).length,
averageAge: users.reduce((sum, user) => sum + user.age, 0) / users.length,
ageGroups: groupUsersByAge(users)
};
}
// Composition of pure functions
function pipe(...functions) {
return function(value) {
return functions.reduce((acc, fn) => fn(acc), value);
};
}
function compose(...functions) {
return function(value) {
return functions.reduceRight((acc, fn) => fn(acc), value);
};
}
// Pure functions for string processing
function trim(str) {
return str.trim();
}
function toLowerCase(str) {
return str.toLowerCase();
}
function removeSpaces(str) {
return str.replace(/\s+/g, '');
}
// Function composition
const normalizeString = pipe(
trim,
toLowerCase,
removeSpaces
);
console.log(normalizeString(' Hello World ')); // "helloworld"
// Adding an element
function addItem(array, item) {
return [...array, item];
}
// Removing element by index
function removeItemByIndex(array, index) {
return array.filter((_, i) => i !== index);
}
// Updating an element
function updateItem(array, index, newItem) {
return array.map((item, i) => i === index ? newItem : item);
}
// Sorting (doesn't modify original array)
function sortBy(array, keyFn) {
return [...array].sort((a, b) => {
const aKey = keyFn(a);
const bKey = keyFn(b);
return aKey < bKey ? -1 : aKey > bKey ? 1 : 0;
});
}
// Grouping
function groupBy(array, keyFn) {
return array.reduce((groups, item) => {
const key = keyFn(item);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
return groups;
}, {});
}
// Updating object property
function updateProperty(obj, key, value) {
return { ...obj, [key]: value };
}
// Removing property
function removeProperty(obj, key) {
const { [key]: removed, ...rest } = obj;
return rest;
}
// Deep update of nested object
function updateNestedProperty(obj, path, value) {
const [head, ...tail] = path;
if (tail.length === 0) {
return { ...obj, [head]: value };
}
return {
...obj,
[head]: updateNestedProperty(obj[head] || {}, tail, value)
};
}
// Merging objects
function mergeObjects(...objects) {
return Object.assign({}, ...objects);
}
// Usage example
const user = {
id: 1,
profile: {
name: 'John',
settings: {
theme: 'dark',
notifications: true
}
}
};
const updatedUser = updateNestedProperty(
user,
['profile', 'settings', 'theme'],
'light'
);
// These operations are impure by definition
// Logging
function logError(error) {
console.error('Error:', error.message);
// Sending to monitoring system
errorTracker.send(error);
}
// DOM manipulation
function updateUI(data) {
document.getElementById('content').innerHTML = data.html;
document.title = data.title;
}
// HTTP requests
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// localStorage operations
function saveToStorage(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// Separating pure logic and side effects
// Pure function for data preparation
function prepareUserData(rawData) {
return {
id: rawData.id,
name: rawData.name.trim(),
email: rawData.email.toLowerCase(),
isValid: isValidEmail(rawData.email)
};
}
// Impure function for saving
function saveUser(userData) {
// Side effect is isolated
return fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
}
// Composition: pure preparation + impure saving
async function createUser(rawData) {
const userData = prepareUserData(rawData);
if (!userData.isValid) {
throw new Error('Invalid user data');
}
return await saveUser(userData);
}
Aspect | Pure Functions | Impure Functions |
---|---|---|
Predictability | Always the same result | Result may vary |
Testing | Easy to test | Require mocks and stubs |
Debugging | Simple debugging | Complex debugging |
Caching | Can be memoized | Cannot be cached |
Parallelism | Safe for parallel execution | May cause race conditions |
Refactoring | Easy to refactor | Hard to refactor |
Code Understanding | Easy to understand logic | Need to consider context |
Reusability | High reusability | Limited reusability |
// ❌ Bad: mixing pure logic and side effects
function processAndSaveUser(userData) {
// Pure logic
const normalizedData = {
name: userData.name.trim(),
email: userData.email.toLowerCase()
};
// Side effect
console.log('Processing user:', normalizedData.name);
// Another side effect
database.save(normalizedData);
return normalizedData;
}
// ✅ Good: separation of concerns
function normalizeUserData(userData) {
return {
name: userData.name.trim(),
email: userData.email.toLowerCase()
};
}
function logUserProcessing(userData) {
console.log('Processing user:', userData.name);
}
function saveUserToDatabase(userData) {
return database.save(userData);
}
// Composition
function processUser(userData) {
const normalizedData = normalizeUserData(userData);
logUserProcessing(normalizedData);
return saveUserToDatabase(normalizedData);
}
// ❌ Bad: data mutation
function addTodoItem(todos, newItem) {
todos.push(newItem); // Modifies original array
return todos;
}
// ✅ Good: immutable addition
function addTodoItem(todos, newItem) {
return [...todos, newItem]; // Returns new array
}
// ❌ Bad: object mutation
function updateUserProfile(user, updates) {
user.name = updates.name; // Modifies original object
user.updatedAt = Date.now();
return user;
}
// ✅ Good: immutable update
function updateUserProfile(user, updates) {
return {
...user,
...updates,
updatedAt: Date.now()
};
}
// ❌ Bad: hidden dependency on global variable
const TAX_RATE = 0.18;
function calculateTotalPrice(price) {
return price * (1 + TAX_RATE); // Depends on global variable
}
// ✅ Good: explicit dependency passing
function calculateTotalPrice(price, taxRate) {
return price * (1 + taxRate);
}
// Or with default value
function calculateTotalPrice(price, taxRate = 0.18) {
return price * (1 + taxRate);
}
/**
* Calculates final cost including discount and tax
* @pure
* @param {number} price - Base price
* @param {number} discountPercent - Discount percentage (0-100)
* @param {number} taxRate - Tax rate (0-1)
* @returns {number} Final cost
*/
function calculateFinalPrice(price, discountPercent, taxRate) {
const discountAmount = price * (discountPercent / 100);
const discountedPrice = price - discountAmount;
return discountedPrice * (1 + taxRate);
}
/**
* Formats user name
* @pure
* @param {string} firstName - First name
* @param {string} lastName - Last name
* @returns {string} Formatted full name
*/
function formatUserName(firstName, lastName) {
return `${firstName.trim()} ${lastName.trim()}`;
}
❌ Problem: Function appears pure but mutates input data
// Seems pure but mutates the array
function sortUsers(users) {
return users.sort((a, b) => a.name.localeCompare(b.name));
}
// Array.sort() modifies the original array!
const originalUsers = [{ name: 'Bob' }, { name: 'Anna' }];
const sortedUsers = sortUsers(originalUsers);
console.log(originalUsers); // Also sorted!
✅ Solution: Create a copy before mutating operations
function sortUsers(users) {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}
// ❌ Bad: depends on current time
function createTimestamp() {
return Date.now();
}
function isWorkingHours() {
const hour = new Date().getHours();
return hour >= 9 && hour <= 18;
}
// ✅ Good: accepts time as parameter
function createTimestamp(date = new Date()) {
return date.getTime();
}
function isWorkingHours(date = new Date()) {
const hour = date.getHours();
return hour >= 9 && hour <= 18;
}
// ❌ Bad: hidden logging
function calculateDiscount(price, percent) {
console.log(`Calculating discount: ${price} * ${percent}%`); // Side effect!
return price * (percent / 100);
}
// ✅ Good: pure function + separate logging
function calculateDiscount(price, percent) {
return price * (percent / 100);
}
function calculateDiscountWithLogging(price, percent) {
const result = calculateDiscount(price, percent);
console.log(`Calculating discount: ${price} * ${percent}% = ${result}`);
return result;
}
// Pure function
function validatePassword(password) {
const errors = [];
if (password.length < 8) {
errors.push('Minimum 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Need uppercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Need digit');
}
return {
isValid: errors.length === 0,
errors
};
}
// Simple tests
function testValidatePassword() {
// Test valid password
const validResult = validatePassword('Password123');
console.assert(validResult.isValid === true);
console.assert(validResult.errors.length === 0);
// Test short password
const shortResult = validatePassword('Pass1');
console.assert(shortResult.isValid === false);
console.assert(shortResult.errors.includes('Minimum 8 characters'));
// Test without uppercase
const noUpperResult = validatePassword('password123');
console.assert(noUpperResult.isValid === false);
console.assert(noUpperResult.errors.includes('Need uppercase letter'));
// Test without digit
const noDigitResult = validatePassword('Password');
console.assert(noDigitResult.isValid === false);
console.assert(noDigitResult.errors.includes('Need digit'));
console.log('All tests passed!');
}
testValidatePassword();
// Testing properties of pure functions
function testMathProperties() {
// Commutativity of addition
for (let i = 0; i < 100; i++) {
const a = Math.random() * 1000;
const b = Math.random() * 1000;
console.assert(add(a, b) === add(b, a));
}
// Associativity of addition
for (let i = 0; i < 100; i++) {
const a = Math.random() * 1000;
const b = Math.random() * 1000;
const c = Math.random() * 1000;
console.assert(add(add(a, b), c) === add(a, add(b, c)));
}
console.log('Mathematical properties verified!');
}
function add(a, b) {
return a + b;
}
Remember: pure functions are not just a good practice, they are the foundation of quality, maintainable and reliable code. Strive for function purity wherever possible, and isolate side effects in separate functions.
Want more articles on interview preparation?
Follow EasyAdvice, bookmark the site, and level up every day 💪