Pure Functions in JavaScript Explained
Learn Pure Functions in JavaScript including side effects, immutability, functional programming, testing benefits, real-world examples, and interview questions.
Pure Functions in JavaScript
Pure Functions are one of the fundamental concepts of Functional Programming.
Modern JavaScript frameworks and libraries heavily promote pure functions:
- React
- Redux
- RxJS
- Functional Programming Libraries
- Serverless Functions
Understanding pure functions helps developers write:
- Predictable Code
- Testable Code
- Reusable Code
- Maintainable Code
What is a Pure Function?
A Pure Function is a function that follows two rules:
1. Same Input Always Produces Same Output
2. No Side Effects
If both conditions are satisfied:
The Function Is Pure
Simple Definition
Pure Function
=
Predictable Function
Why Do We Need Pure Functions?
Imagine:
function add(a, b) {
return a + b;
}
Input:
add(10, 20);
Output:
30
Every time:
add(10, 20);
Output remains:
30
This predictability makes software easier to maintain.
Characteristics of Pure Functions
A Pure Function:
✅ Same Input → Same Output
✅ No Global Variable Dependency
✅ No External State Modification
✅ No Database Updates
✅ No API Calls
✅ No DOM Manipulation
✅ No Logging Side Effects
Pure Function Architecture
Input
|
v
Pure Function
|
v
Output
No external interaction occurs.
Example of a Pure Function
function multiply(a, b) {
return a * b;
}
Input:
multiply(5, 2);
Output:
10
Every execution:
5 × 2 = 10
Always produces the same result.
Pure Function Flow
5, 2
|
v
multiply()
|
v
10
Predictable behavior.
Another Pure Function
function calculateTax(amount) {
return amount * 0.18;
}
Input:
calculateTax(100);
Output:
18
Every time.
What is a Side Effect?
A Side Effect occurs when a function changes something outside itself.
Examples:
Modify Global Variables
Database Updates
API Calls
File Writes
DOM Updates
Console Logging
Changing Objects
Impure Function Example
let count = 0;
function increment() {
count++;
return count;
}
Output changes:
increment();
increment();
increment();
Result:
1
2
3
Not pure.
Why Is It Impure?
Because:
Depends On External State
Modifies External State
Impure Function Architecture
Global State
|
v
Function
|
v
Changes Global State
Example: Console Log Side Effect
function greet(name) {
console.log(name);
}
This is considered:
Impure Function
Because:
Writes To External System
(console)
Example: DOM Manipulation
function updateTitle() {
document.title =
"CodeWithVenu";
}
Impure because:
Modifies Browser State
Example: API Call
function getUsers() {
return fetch("/users");
}
Impure because:
Depends On External Service
Results may change.
Pure vs Impure Function
Pure
function square(num) {
return num * num;
}
Impure
let multiplier = 2;
function square(num) {
return num * multiplier;
}
Depends on external variable.
Pure Function Comparison
| Feature | Pure Function | Impure Function |
|---|---|---|
| Same Input Same Output | Yes | Not Always |
| Side Effects | No | Yes |
| Easy Testing | Yes | Difficult |
| Predictable | Yes | No |
| Depends On External State | No | Yes |
Pure Functions and Immutability
Pure functions should not modify input data.
Bad:
function addUser(users, user) {
users.push(user);
return users;
}
Impure.
Why?
Because:
Original Array Modified
Better Approach
function addUser(users, user) {
return [...users, user];
}
Pure.
Memory Flow
Bad:
Array
|
v
Modified
Good:
Old Array
|
v
Create New Array
Real World Example
Order Calculation
function calculateTotal(
price,
tax
) {
return price + tax;
}
Input:
calculateTotal(100, 20);
Output:
120
Always.
React and Pure Functions
React components are ideally pure.
Example:
function UserProfile({name}) {
return <h1>{name}</h1>;
}
Input:
name="Venu"
Output:
<h1>Venu</h1>
Same input.
Same output.
Redux and Pure Functions
Reducers must be pure.
Good:
function reducer(
state,
action
) {
return {
...state,
count: state.count + 1
};
}
Bad Redux Reducer
state.count++;
Mutates state.
Not pure.
Benefits of Pure Functions
Predictable
Same Input
=
Same Output
Easy Testing
expect(add(10,20))
.toBe(30);
No setup required.
Easy Debugging
No hidden dependencies.
Reusable
Works anywhere.
Parallel Processing
Safe because no shared state.
Functional Programming
Pure Functions are a core principle of:
Functional Programming
Other concepts:
Immutability
Higher Order Functions
Function Composition
Declarative Programming
Common Mistakes
Modifying Parameters
Bad:
function update(user) {
user.name = "Venu";
}
Impure.
Using Global Variables
Bad:
let taxRate = 0.18;
function calculate(price) {
return price * taxRate;
}
Depends on external state.
Random Values
function generateId() {
return Math.random();
}
Impure.
Same input does not produce same output.
Current Time
function getTime() {
return Date.now();
}
Impure.
Output changes every call.
Pure Version Example
Instead of:
function calculate() {
return Date.now();
}
Use:
function calculate(time) {
return time + 100;
}
Pass external data as parameters.
Real World Example
Bad:
let discount = 0.10;
function calculate(price) {
return price - (price * discount);
}
Better
function calculate(
price,
discount
) {
return price - (price * discount);
}
Pure.
Interview Questions
What is a Pure Function?
A function that:
Same Input → Same Output
No Side Effects
What is a Side Effect?
Any operation that affects external state.
Examples:
- API Calls
- DOM Updates
- Logging
- Global Variables
Why Are Pure Functions Important?
They are:
- Predictable
- Testable
- Reusable
Is console.log a Side Effect?
Yes.
Is Math.random Pure?
No.
Output changes.
Is Date.now Pure?
No.
Output changes.
Are React Components Pure?
Ideally yes.
Why Are Redux Reducers Pure?
To maintain predictable state updates.
Cheat Sheet
Pure Function
-------------
Same Input
=
Same Output
No Side Effects
Examples
--------
add()
multiply()
calculateTax()
Impure Examples
---------------
console.log()
fetch()
Date.now()
Math.random()
DOM Updates
Benefits
--------
Predictable
Testable
Reusable
Maintainable
Functional Programming
----------------------
Pure Functions
Immutability
Higher Order Functions
Composition
Summary
Pure Functions are one of the most important principles in modern JavaScript development.
A Pure Function:
Same Input
=
Same Output
AND
No Side Effects
Benefits:
- Easier Testing
- Better Debugging
- Predictable Behavior
- Reusable Logic
Pure Functions are heavily used in:
- React
- Redux
- Functional Programming
- Modern JavaScript Applications
Mastering pure functions is essential before learning:
- Function Composition
- Currying
- Redux
- React Performance Optimization
- Functional Programming Patterns
The best JavaScript developers strive to make most business logic pure whenever possible.
. Pass external dependencies as parameters
// ❌ IMPURE
let taxRate = 0.08;
function calculateTax(amount) {
return amount * taxRate;
}
// ✅ PURE
function calculateTax(amount, taxRate) {
return amount * taxRate;
}
- Return new values instead of mutating
// ❌ IMPURE
function addItem(array, item) {
array.push(item);
return array;
}
// ✅ PURE
function addItem(array, item) {
return [...array, item];
}
- Separate side effects from logic
// ❌ IMPURE
function processOrder(order) {
const total = calculateTotal(order);
saveToDatabase(order); // Side effect
return total;
}
// ✅ PURE (logic) + Impure (effects)
function calculateOrderTotal(order) {
return order.items.reduce((sum, item) =>
sum + item.price, 0
);
}
function processOrder(order) {
const total = calculateOrderTotal(order);
saveToDatabase({ ...order, total });
return total;
}
- Accept time/random values as parameters
// ❌ IMPURE
function createTimestamp() {
return Date.now();
}
// ✅ PURE
function formatTimestamp(timestamp) {
return new Date(timestamp).toISOString();
}
// Usage
const timestamp = Date.now(); // Impure (isolated)
const formatted = formatTimestamp(timestamp); // Pure
Q5: Explain the difference between pure and impure functions in Redux.
Answer:
Redux requires pure reducers for predictable state management:
// ❌ IMPURE REDUCER - Mutates state
function badReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
state.count++; // Mutation!
return state;
default:
return state;
}
}
// ✅ PURE REDUCER - Returns new state
function goodReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
default:
return state;
}
}
// Why pure reducers matter:
// 1. Time-travel debugging works
// 2. State changes are trackable
// 3. Performance optimization possible
// 4. Predictable state updates
Complex state updates:
// ✅ PURE - Nested state update
function userReducer(state = { users: [] }, action) {
switch (action.type) {
case 'UPDATE_USER':
return {
...state,
users: state.users.map(user =>
user.id === action.payload.id
? { ...user, ...action.payload.updates }
: user
)
};
case 'ADD_USER':
return {
...state,
users: [...state.users, action.payload]
};
case 'REMOVE_USER':
return {
...state,
users: state.users.filter(user =>
user.id !== action.payload
)
};
default:
return state;
}
}
Q6: How do pure functions help with testing?
Answer:
Pure functions are extremely easy to test:
// Pure function
function calculateDiscount(price, discountPercent) {
return price * (1 - discountPercent / 100);
}
// Simple test - no setup needed
describe('calculateDiscount', () => {
test('applies 10% discount', () => {
expect(calculateDiscount(100, 10)).toBe(90);
});
test('applies 20% discount', () => {
expect(calculateDiscount(100, 20)).toBe(80);
});
test('handles zero discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});
});
// Impure function
let globalTaxRate = 0.08;
function calculateTax(amount) {
return amount * globalTaxRate;
}
// Complex test - requires setup
describe('calculateTax', () => {
beforeEach(() => {
globalTaxRate = 0.08; // Reset global state
});
test('calculates tax', () => {
expect(calculateTax(100)).toBe(8);
});
test('uses updated tax rate', () => {
globalTaxRate = 0.10;
expect(calculateTax(100)).toBe(10);
});
});
Benefits for testing:
- No mocking required
- No setup/teardown needed
- Tests are isolated
- Easy to write property-based tests
- Deterministic results
12. Cheat Sheet
┌─────────────────────────────────────────────────────┐
│ PURE FUNCTIONS CHEAT SHEET │
├─────────────────────────────────────────────────────┤
│ │
│ DEFINITION │
│ ────────── │
│ 1. Same Input → Same Output (Deterministic) │
│ 2. No Side Effects │
│ │
│ CHARACTERISTICS │
│ ─────────────── │
│ ✅ Predictable │
│ ✅ Testable │
│ ✅ Cacheable (Memoizable) │
│ ✅ Parallelizable │
│ ✅ Referentially Transparent │
│ │
│ SIDE EFFECTS TO AVOID │
│ ────────────────────── │
│ ❌ Modifying global variables │
│ ❌ Modifying parameters │
│ ❌ HTTP requests │
│ ❌ Database operations │
│ ❌ File I/O │
│ ❌ DOM manipulation │
│ ❌ Console logging │
│ ❌ Math.random() │
│ ❌ Date.now() │
│ │
│ PURE EXAMPLES │
│ ────────────── │
│ function add(a, b) { │
│ return a + b; │
│ } │
│ │
│ function multiply(a, b) { │
│ return a * b; │
│ } │
│ │
│ function addItem(arr, item) { │
│ return [...arr, item]; │
│ } │
│ │
│ function updateUser(user, updates) { │
│ return { ...user, ...updates }; │
│ } │
│ │
│ IMPURE EXAMPLES │
│ ──────────────── │
│ let count = 0; │
│ function increment() { │
│ count++; // Modifies external state │
│ return count; │
│ } │
│ │
│ function log(msg) { │
│ console.log(msg); // Side effect │
│ } │
│ │
│ function fetchData() { │
│ return fetch('/api'); // Side effect │
│ } │
│ │
│ MAKING FUNCTIONS PURE │
│ ────────────────────── │
│ 1. Pass dependencies as parameters │
│ 2. Return new values (don't mutate) │
│ 3. Separate logic from side effects │
│ 4. Accept time/random as parameters │
│ │
│ IMMUTABILITY PATTERNS │
│ ────────────────────── │
│ // Arrays │
│ [...arr, item] // Add │
│ arr.filter(x => x !== y) // Remove │
│ arr.map(fn) // Transform │
│ │
│ // Objects │
│ { ...obj, key: value } // Update │
│ { ...obj, ...updates } // Merge │
│ const { key, ...rest } = obj // Remove │
│ │
│ REACT USAGE │
│ ──────────── │
│ // Pure component │
│ function UserCard({ name, email }) { │
│ return <div>{name}: {email}</div>; │
│ } │
│ │
│ // Memoization │
│ const Memoized = React.memo(UserCard); │
│ │
│ REDUX USAGE │
│ ──────────── │
│ function reducer(state, action) { │
│ switch (action.type) { │
│ case 'ADD': │
│ return { │
│ ...state, │
│ items: [...state.items, action.payload]│
│ }; │
│ default: │
│ return state; │
│ } │
│ } │
│ │
│ TESTING │
│ ─────── │
│ // Pure function - easy to test │
│ test('adds numbers', () => { │
│ expect(add(2, 3)).toBe(5); │
│ }); │
│ │
│ // No setup, no mocking needed │
│ │
│ PERFORMANCE │
│ ─────────── │
│ // Memoization │
│ const memoized = memoize(expensiveFn); │
│ │
│ // Lazy evaluation │
│ function* lazyMap(arr, fn) { │
│ for (const item of arr) { │
│ yield fn(item); │
│ } │
│ } │
│ │
│ BENEFITS │
│ ──────── │
│ • Easier to reason about │
│ • Simpler to test │
│ • Better for debugging │
│ • Enables optimization │
│ • Safe for concurrency │
│ • Composable │
│ │
│ WHEN TO USE │
│ ──────────── │
│ ✅ Business logic │
│ ✅ Data transformations │
│ ✅ Calculations │
│ ✅ Validation │
│ ✅ Formatting │
│ ✅ React components │
│ ✅ Redux reducers │
│ │
│ WHEN IMPURE IS OK │
│ ────────────────── │
│ • API calls (isolate in separate layer) │
│ • Database operations │
│ • File I/O │
│ • Logging (development) │
│ • Event handlers │
│ │
└─────────────────────────────────────────────────────┘
13. Summary
Pure functions are a cornerstone of modern JavaScript development and functional programming. They provide predictability, testability, and maintainability that make complex applications easier to build and maintain.
Key Takeaways
┌─────────────────────────────────────────────────────┐
│ PURE FUNCTIONS SUMMARY │
├─────────────────────────────────────────────────────┤
│ │
│ TWO FUNDAMENTAL RULES │
│ ───────────────────── │
│ 1. Deterministic Behavior │
│ Same Input → Same Output (Always) │
│ │
│ 2. No Side Effects │
│ No External State Changes │
│ │
│ CORE BENEFITS │
│ ───────────── │
│ ✅ Predictable - Easy to reason about │
│ ✅ Testable - Simple unit tests │
│ ✅ Debuggable - No hidden dependencies │
│ ✅ Reusable - Works anywhere │
│ ✅ Optimizable - Memoization possible │
│ ✅ Parallelizable - Thread-safe │
│ │
│ REAL-WORLD APPLICATIONS │
│ ─────────────────────── │
│ • React Components │
│ • Redux Reducers │
│ • Data Transformations │
│ • Business Logic │
│ • Validation Functions │
│ • Utility Functions │
│ │
│ BEST PRACTICES │
│ ─────────────── │
│ 1. Pass dependencies as parameters │
│ 2. Return new values (immutability) │
│ 3. Separate pure logic from side effects │
│ 4. Use pure functions for business logic │
│ 5. Isolate impure operations │
│ 6. Test pure functions extensively │
│ │
└─────────────────────────────────────────────────────┘
Why Pure Functions Matter
- Code Quality: Pure functions lead to cleaner, more maintainable code
- Testing: Dramatically simplifies testing and debugging
- Performance: Enables powerful optimizations like memoization
- Concurrency: Safe for parallel execution
- Functional Programming: Foundation for advanced FP concepts
Modern JavaScript Ecosystem
Pure functions are essential in:
- React: Components should be pure for optimal performance
- Redux: Reducers must be pure for predictable state management
- Functional Libraries: Lodash, Ramda, RxJS rely on pure functions
- Testing: Jest, Mocha benefit from pure function simplicity
- Build Tools: Tree-shaking and dead code elimination work better with pure functions
Next Steps
After mastering pure functions, explore:
- Function Composition: Combining pure functions
- Currying: Partial application of functions
- Immutability: Deep immutability patterns
- Functional Programming: Advanced FP concepts
- Performance: Memoization and optimization techniques
Final Thoughts
// Strive for purity in your functions
// Your future self will thank you
// ✅ Pure, predictable, testable
function calculateTotal(items, taxRate) {
const subtotal = items.reduce((sum, item) =>
sum + item.price, 0
);
return subtotal * (1 + taxRate);
}
// This simple function is:
// • Easy to understand
// • Easy to test
// • Easy to reuse
// • Easy to optimize
// • Easy to maintain
// That's the power of pure functions!
Pure functions are not just a programming technique—they're a mindset that leads to better software design. By embracing pure functions, you write code that is more reliable, maintainable, and enjoyable to work with.
Related Topics:
Practice Resources:
- Write pure versions of common array methods
- Refactor impure code to pure functions
- Build a Redux reducer with pure functions
- Create a memoization utility
- Implement functional data transformations
Master pure functions to write better JavaScript code! 🚀