javascript
exercises
exercises.js⚡javascript
/**
* =====================================================
* 5.9 PURE FUNCTIONS & SIDE EFFECTS - EXERCISES
* =====================================================
* Practice writing predictable, testable functions
*/
/**
* Exercise 1: Identify Pure vs Impure
*
* Mark each function as Pure or Impure and explain why.
*/
console.log('Exercise 1: Identify Pure vs Impure');
// Function A
function add(a, b) {
return a + b;
}
// TODO: Pure or Impure? Why?
// Function B
let total = 0;
function addToTotal(n) {
total += n;
return total;
}
// TODO: Pure or Impure? Why?
// Function C
function getRandomNumber(max) {
return Math.floor(Math.random() * max);
}
// TODO: Pure or Impure? Why?
// Function D
function formatDate(date) {
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
}
// TODO: Pure or Impure? Why?
/**
* Exercise 2: Convert to Pure - Array Operations
*
* Convert these impure functions to pure functions.
*/
console.log('\nExercise 2: Convert to Pure - Arrays');
// ❌ Impure - mutates array
function addItemImpure(arr, item) {
arr.push(item);
return arr;
}
// TODO: Write pure version
function addItem(arr, item) {
// Your code here
}
// ❌ Impure - mutates array
function removeLastImpure(arr) {
arr.pop();
return arr;
}
// TODO: Write pure version
function removeLast(arr) {
// Your code here
}
// Test:
// const original = [1, 2, 3];
// console.log(addItem(original, 4)); // [1, 2, 3, 4]
// console.log(original); // [1, 2, 3] (unchanged)
/**
* Exercise 3: Convert to Pure - Object Operations
*
* Convert these impure functions to pure functions.
*/
console.log('\nExercise 3: Convert to Pure - Objects');
// ❌ Impure - mutates object
function updateNameImpure(person, newName) {
person.name = newName;
return person;
}
// TODO: Write pure version
function updateName(person, newName) {
// Your code here
}
// ❌ Impure - mutates nested object
function updateCityImpure(person, newCity) {
person.address.city = newCity;
return person;
}
// TODO: Write pure version
function updateCity(person, newCity) {
// Your code here
}
// Test:
// const person = { name: "Alice", address: { city: "NYC" } };
// console.log(updateName(person, "Bob")); // { name: "Bob", ... }
// console.log(person.name); // "Alice" (unchanged)
/**
* Exercise 4: Pure Math Functions
*
* Write pure functions for these mathematical operations.
*/
console.log('\nExercise 4: Pure Math Functions');
// TODO: Calculate factorial
function factorial(n) {
// Your code here
}
// TODO: Calculate average
function average(numbers) {
// Your code here
}
// TODO: Clamp value between min and max
function clamp(value, min, max) {
// Your code here
}
// TODO: Round to N decimal places
function roundTo(value, decimals) {
// Your code here
}
// Test:
// console.log(factorial(5)); // 120
// console.log(average([1,2,3,4,5])); // 3
// console.log(clamp(15, 0, 10)); // 10
// console.log(roundTo(3.14159, 2)); // 3.14
/**
* Exercise 5: Pure String Functions
*
* Write pure functions for string operations.
*/
console.log('\nExercise 5: Pure String Functions');
// TODO: Create URL slug from text
function slugify(text) {
// "Hello World!" → "hello-world"
}
// TODO: Truncate with ellipsis
function truncate(str, maxLength) {
// "Hello World", 8 → "Hello..."
}
// TODO: Capitalize first letter of each word
function titleCase(str) {
// "hello world" → "Hello World"
}
// Test:
// console.log(slugify("Hello World!")); // "hello-world"
// console.log(truncate("Hello World", 8)); // "Hello..."
// console.log(titleCase("hello world")); // "Hello World"
/**
* Exercise 6: Todo List Pure Functions
*
* Implement pure functions for a todo list.
*/
console.log('\nExercise 6: Todo List');
// TODO: Add a new todo
function addTodo(todos, text) {
// Return new array with added todo
// { id: Date.now(), text, completed: false }
}
// TODO: Toggle a todo's completed status
function toggleTodo(todos, id) {
// Return new array with toggled todo
}
// TODO: Remove a todo by id
function removeTodo(todos, id) {
// Return new array without the todo
}
// TODO: Get only completed todos
function getCompleted(todos) {
// Return new array of completed todos
}
// Test:
// let todos = [];
// todos = addTodo(todos, "Learn JS");
// todos = addTodo(todos, "Build app");
// console.log(todos);
/**
* Exercise 7: Shopping Cart Pure Functions
*
* Implement pure functions for a shopping cart.
*/
console.log('\nExercise 7: Shopping Cart');
// TODO: Add item to cart (or increase quantity if exists)
function addToCart(cart, item) {
// Your code here
}
// TODO: Remove item from cart
function removeFromCart(cart, itemId) {
// Your code here
}
// TODO: Update item quantity
function updateQuantity(cart, itemId, quantity) {
// Your code here
}
// TODO: Calculate cart total
function getTotal(cart) {
// Your code here
}
// Test:
// let cart = [];
// cart = addToCart(cart, { id: 1, name: "Widget", price: 10 });
// cart = addToCart(cart, { id: 2, name: "Gadget", price: 25 });
// console.log(getTotal(cart)); // 35
/**
* Exercise 8: Data Transformation Pipeline
*
* Create pure functions that can be chained together.
*/
console.log('\nExercise 8: Data Pipeline');
const users = [
{ id: 1, name: 'Alice', age: 25, active: true },
{ id: 2, name: 'Bob', age: 30, active: false },
{ id: 3, name: 'Charlie', age: 35, active: true },
];
// TODO: Filter active users
function filterActive(users) {
// Your code here
}
// TODO: Sort by age (ascending)
function sortByAge(users) {
// Your code here
}
// TODO: Extract names only
function getNames(users) {
// Your code here
}
// TODO: Use all three in a pipeline
// const result = getNames(sortByAge(filterActive(users)));
// console.log(result); // ["Alice", "Charlie"]
/**
* Exercise 9: Memoization
*
* Implement memoization for pure functions.
*/
console.log('\nExercise 9: Memoization');
function memoize(fn) {
// TODO: Implement memoization
// Cache results based on arguments
}
// Test:
// let callCount = 0;
// const expensiveFn = memoize((n) => {
// callCount++;
// return n * n;
// });
// console.log(expensiveFn(5)); // 25
// console.log(expensiveFn(5)); // 25 (cached)
// console.log(callCount); // 1
/**
* Exercise 10: Dependency Injection
*
* Refactor to make the function pure using dependency injection.
*/
console.log('\nExercise 10: Dependency Injection');
// ❌ Impure - uses Date directly
function getAgeImpure(birthYear) {
return new Date().getFullYear() - birthYear;
}
// TODO: Make it pure by injecting current year
function getAge(birthYear, currentYear) {
// Your code here
}
// ❌ Impure - uses random
function shuffleImpure(arr) {
return [...arr].sort(() => Math.random() - 0.5);
}
// TODO: Make it pure by injecting random function
function shuffle(arr, randomFn) {
// Your code here
}
// Test:
// console.log(getAge(1990, 2024)); // 34
// console.log(shuffle([1,2,3], () => 0.5)); // Predictable
// =====================================================
// ADVANCED EXERCISES
// =====================================================
/**
* Bonus 1: Deep Update
*
* Pure function to update nested property by path.
*/
console.log('\nBonus 1: Deep Update');
function updateIn(obj, path, value) {
// TODO: Implement
// updateIn({ a: { b: 1 } }, ['a', 'b'], 2) → { a: { b: 2 } }
}
// Test:
// const state = { user: { profile: { name: "Alice" } } };
// const newState = updateIn(state, ['user', 'profile', 'name'], 'Bob');
// console.log(newState.user.profile.name); // "Bob"
// console.log(state.user.profile.name); // "Alice"
/**
* Bonus 2: Compose Function
*
* Create a compose function for pure function composition.
*/
console.log('\nBonus 2: Compose');
function compose(...fns) {
// TODO: Implement right-to-left composition
}
function pipe(...fns) {
// TODO: Implement left-to-right composition
}
// Test:
// const addOne = x => x + 1;
// const double = x => x * 2;
// const stringify = x => `Result: ${x}`;
//
// const process = pipe(addOne, double, stringify);
// console.log(process(5)); // "Result: 12"
/**
* Bonus 3: Immutable State Manager
*
* Create a simple state manager using only pure functions.
*/
console.log('\nBonus 3: State Manager');
function createStore(initialState, reducer) {
// TODO: Implement
// return { getState, dispatch }
}
// Test:
// const reducer = (state, action) => {
// switch (action.type) {
// case 'INCREMENT': return { ...state, count: state.count + 1 };
// case 'DECREMENT': return { ...state, count: state.count - 1 };
// default: return state;
// }
// };
//
// const store = createStore({ count: 0 }, reducer);
// store.dispatch({ type: 'INCREMENT' });
// console.log(store.getState()); // { count: 1 }
// =====================================================
// SOLUTIONS (Uncomment to check your answers)
// =====================================================
/*
// Exercise 1 Solutions:
// A: Pure - same input always gives same output, no side effects
// B: Impure - modifies external variable 'total'
// C: Impure - non-deterministic (random)
// D: Pure - if passed the same Date object, returns same string
// Exercise 2 Solution:
function addItem(arr, item) {
return [...arr, item];
}
function removeLast(arr) {
return arr.slice(0, -1);
}
// Exercise 3 Solution:
function updateName(person, newName) {
return { ...person, name: newName };
}
function updateCity(person, newCity) {
return {
...person,
address: { ...person.address, city: newCity }
};
}
// Exercise 4 Solution:
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
function average(numbers) {
if (numbers.length === 0) return 0;
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function roundTo(value, decimals) {
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
}
// Exercise 5 Solution:
function slugify(text) {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-');
}
function truncate(str, maxLength) {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + '...';
}
function titleCase(str) {
return str.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}
// Exercise 6 Solution:
function addTodo(todos, text) {
return [...todos, { id: Date.now(), text, completed: false }];
}
function toggleTodo(todos, id) {
return todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
}
function removeTodo(todos, id) {
return todos.filter(todo => todo.id !== id);
}
function getCompleted(todos) {
return todos.filter(todo => todo.completed);
}
// Exercise 7 Solution:
function addToCart(cart, item) {
const existing = cart.find(i => i.id === item.id);
if (existing) {
return cart.map(i =>
i.id === item.id ? { ...i, quantity: (i.quantity || 1) + 1 } : i
);
}
return [...cart, { ...item, quantity: 1 }];
}
function removeFromCart(cart, itemId) {
return cart.filter(item => item.id !== itemId);
}
function updateQuantity(cart, itemId, quantity) {
if (quantity <= 0) return removeFromCart(cart, itemId);
return cart.map(item =>
item.id === itemId ? { ...item, quantity } : item
);
}
function getTotal(cart) {
return cart.reduce((sum, item) => sum + item.price * (item.quantity || 1), 0);
}
// Exercise 8 Solution:
function filterActive(users) {
return users.filter(u => u.active);
}
function sortByAge(users) {
return [...users].sort((a, b) => a.age - b.age);
}
function getNames(users) {
return users.map(u => u.name);
}
// Exercise 9 Solution:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key);
};
}
// Exercise 10 Solution:
function getAge(birthYear, currentYear) {
return currentYear - birthYear;
}
function shuffle(arr, randomFn) {
return [...arr].sort(() => randomFn() - 0.5);
}
// Bonus 1 Solution:
function updateIn(obj, path, value) {
const [first, ...rest] = path;
if (rest.length === 0) {
return { ...obj, [first]: value };
}
return {
...obj,
[first]: updateIn(obj[first] || {}, rest, value)
};
}
// Bonus 2 Solution:
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
function pipe(...fns) {
return function(value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
// Bonus 3 Solution:
function createStore(initialState, reducer) {
let state = initialState;
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
return state;
}
};
}
*/