Functions — are the foundation of programming and one of the most important tools in JavaScript. They solve fundamental development problems:
Without functions, code turns into “spaghetti” — a tangled mess of instructions that is impossible to maintain.
Problem without functions:
// Bad: code duplication
let user1Name = "John";
let user1Email = "john@example.com";
console.log("Hello, " + user1Name + "!");
console.log("Your email: " + user1Email);
let user2Name = "Mary";
let user2Email = "mary@example.com";
console.log("Hello, " + user2Name + "!");
console.log("Your email: " + user2Email);
let user3Name = "Peter";
let user3Email = "peter@example.com";
console.log("Hello, " + user3Name + "!");
console.log("Your email: " + user3Email);
Solution with functions:
// Good: reusable function
function greetUser(name, email) {
console.log(`Hello, ${name}!`);
console.log(`Your email: ${email}`);
}
greetUser("John", "john@example.com");
greetUser("Mary", "mary@example.com");
greetUser("Peter", "peter@example.com");
Problem:
// Bad: everything in one place
let price = 1000;
let discount = 0.1;
let tax = 0.18;
let shipping = 200;
let discountAmount = price * discount;
let priceAfterDiscount = price - discountAmount;
let taxAmount = priceAfterDiscount * tax;
let finalPrice = priceAfterDiscount + taxAmount + shipping;
console.log("Final price: " + finalPrice);
Solution:
// Good: broken into logical blocks
function calculateDiscount(price, discountRate) {
return price * discountRate;
}
function calculateTax(amount, taxRate) {
return amount * taxRate;
}
function calculateFinalPrice(price, discount, tax, shipping) {
const discountAmount = calculateDiscount(price, discount);
const priceAfterDiscount = price - discountAmount;
const taxAmount = calculateTax(priceAfterDiscount, tax);
return priceAfterDiscount + taxAmount + shipping;
}
const finalPrice = calculateFinalPrice(1000, 0.1, 0.18, 200);
console.log(`Final price: ${finalPrice}`);
Problem:
// Bad: global variables
var counter = 0;
var step = 1;
// Somewhere in the code
counter += step;
// In another place someone accidentally changed
step = 10;
// Now the counter works incorrectly
counter += step; // +10 instead of +1
Solution:
// Good: encapsulation in functions
function createCounter(initialValue = 0, stepValue = 1) {
let counter = initialValue;
let step = stepValue;
return {
increment() {
counter += step;
return counter;
},
decrement() {
counter -= step;
return counter;
},
getValue() {
return counter;
}
};
}
const myCounter = createCounter();
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
Purpose: Main way to create reusable code blocks
function calculateArea(width, height) {
return width * height;
}
// Available before declaration thanks to hoisting
console.log(getGreeting("World")); // "Hello, World!"
function getGreeting(name) {
return `Hello, ${name}!`;
}
Purpose: Creating functions as values, conditional declaration
// Assigning function to variable
const multiply = function(a, b) {
return a * b;
};
// Conditional function creation
let operation;
if (userPreference === 'add') {
operation = function(a, b) { return a + b; };
} else {
operation = function(a, b) { return a - b; };
}
Purpose: Concise syntax, especially for callbacks and functional methods
// Short notation
const square = x => x * x;
// Great for arrays
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// Preserve this context
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++; // this points to Timer instance
console.log(this.seconds);
}, 1000);
}
}
Purpose: Functions associated with object data
const user = {
name: "Anna",
age: 25,
// Object method
introduce() {
return `My name is ${this.name}, I am ${this.age} years old`;
},
// Method with logic
canVote() {
return this.age >= 18;
},
// State changing method
celebrateBirthday() {
this.age++;
console.log(`Happy birthday! Now I am ${this.age}`);
}
};
// Data validation
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Formatting
function formatCurrency(amount, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(amount);
}
// Data transformation
function normalizeUserData(rawUser) {
return {
id: rawUser.user_id,
name: rawUser.full_name?.trim(),
email: rawUser.email_address?.toLowerCase(),
isActive: rawUser.status === 'active'
};
}
// Creating elements
function createElement(tag, className, textContent) {
const element = document.createElement(tag);
if (className) element.className = className;
if (textContent) element.textContent = textContent;
return element;
}
// Event handling
function handleFormSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const userData = Object.fromEntries(formData);
if (validateUserData(userData)) {
submitUserData(userData);
} else {
showValidationErrors(userData);
}
}
// Animations
function fadeIn(element, duration = 300) {
element.style.opacity = 0;
element.style.display = 'block';
const start = performance.now();
function animate(currentTime) {
const elapsed = currentTime - start;
const progress = Math.min(elapsed / duration, 1);
element.style.opacity = progress;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Working with API
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
return normalizeUserData(userData);
} catch (error) {
console.error('Error loading user:', error);
throw error;
}
}
// Debouncing
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage
const debouncedSearch = debounce(function(query) {
searchAPI(query);
}, 300);
Higher-order functions — functions that:
// Function accepts another function
function processArray(array, processor) {
const result = [];
for (let item of array) {
result.push(processor(item));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const squared = processArray(numbers, x => x * x);
const doubled = processArray(numbers, x => x * 2);
// Function returns another function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Function composition
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;
const addOneThenDouble = compose(multiplyByTwo, addOne);
console.log(addOneThenDouble(3)); // 8 (3 + 1) * 2
const Calculator = (function() {
// Private variables
let history = [];
// Private functions
function addToHistory(operation, result) {
history.push({ operation, result, timestamp: Date.now() });
}
// Public API
return {
add(a, b) {
const result = a + b;
addToHistory(`${a} + ${b}`, result);
return result;
},
subtract(a, b) {
const result = a - b;
addToHistory(`${a} - ${b}`, result);
return result;
},
getHistory() {
return [...history]; // Return copy
},
clearHistory() {
history = [];
}
};
})();
function createUser(name, email, role = 'user') {
return {
name,
email,
role,
// User methods
getDisplayName() {
return this.name;
},
hasPermission(permission) {
const permissions = {
user: ['read'],
admin: ['read', 'write', 'delete'],
moderator: ['read', 'write']
};
return permissions[this.role]?.includes(permission) || false;
},
updateProfile(newData) {
Object.assign(this, newData);
}
};
}
// Usage
const user = createUser("Anna", "anna@example.com");
const admin = createUser("John", "john@example.com", "admin");
// Base function
function greet(name) {
return `Hello, ${name}!`;
}
// Decorators
function withLogging(func) {
return function(...args) {
console.log(`Function call with arguments:`, args);
const result = func.apply(this, args);
console.log(`Result:`, result);
return result;
};
}
function withTiming(func) {
return function(...args) {
const start = performance.now();
const result = func.apply(this, args);
const end = performance.now();
console.log(`Execution took ${end - start} ms`);
return result;
};
}
// Applying decorators
const decoratedGreet = withTiming(withLogging(greet));
decoratedGreet("World");
Approach | Advantages | Disadvantages | When to Use |
---|---|---|---|
Without functions | Simplicity for beginners | Duplication, maintenance complexity | Only for learning |
Regular functions | Hoisting, readability | More code | Main application logic |
Arrow functions | Conciseness, lexical this | No hoisting, no arguments | Callbacks, functional programming |
Class methods | OOP approach, encapsulation | Complexity for simple tasks | Complex objects with state |
Higher-order functions | Flexibility, reusability | Understanding complexity | Libraries, utilities |
// ❌ Bad: function does too much
function processUser(userData) {
// Validation
if (!userData.email || !userData.name) {
throw new Error('Incomplete data');
}
// Formatting
userData.email = userData.email.toLowerCase();
userData.name = userData.name.trim();
// Saving to database
database.save(userData);
// Sending email
emailService.sendWelcome(userData.email);
// Logging
logger.log(`User ${userData.name} created`);
}
// ✅ Good: each function solves one task
function validateUserData(userData) {
if (!userData.email || !userData.name) {
throw new Error('Incomplete data');
}
}
function formatUserData(userData) {
return {
...userData,
email: userData.email.toLowerCase(),
name: userData.name.trim()
};
}
function saveUser(userData) {
return database.save(userData);
}
function sendWelcomeEmail(email) {
return emailService.sendWelcome(email);
}
function logUserCreation(name) {
logger.log(`User ${name} created`);
}
// Function composition
async function createUser(userData) {
validateUserData(userData);
const formattedData = formatUserData(userData);
const savedUser = await saveUser(formattedData);
await sendWelcomeEmail(savedUser.email);
logUserCreation(savedUser.name);
return savedUser;
}
// ❌ Bad: function with side effects
let counter = 0;
function incrementAndGet() {
counter++; // Modifies external state
console.log(counter); // Side effect
return counter;
}
// ✅ Good: pure function
function increment(value) {
return value + 1; // Only returns result
}
// ✅ Good: pure function for arrays
function addItem(array, item) {
return [...array, item]; // Doesn't modify original array
}
// ✅ Good: pure function for objects
function updateUser(user, updates) {
return { ...user, ...updates }; // Returns new object
}
// ❌ Bad: unclear names
function calc(a, b, c) {
return a * b * c / 100;
}
function proc(data) {
return data.filter(x => x.active).map(x => x.name);
}
// ✅ Good: descriptive names
function calculateTotalPrice(price, quantity, taxRate) {
return price * quantity * taxRate / 100;
}
function getActiveUserNames(users) {
return users
.filter(user => user.active)
.map(user => user.name);
}
// ✅ Good: predicate functions
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function hasPermission(user, permission) {
return user.permissions.includes(permission);
}
// ✅ Good: explicit error handling
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('JSON parsing error:', error.message);
return null;
}
}
// ✅ Good: parameter validation
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero is impossible');
}
return a / b;
}
// ✅ Good: async/await with error handling
async function fetchUserSafely(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`User not found: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error loading user:', error);
return null;
}
}
❌ Problem: Functions that do too much
Signs:
✅ Solution: Break into smaller functions
// ❌ Bad: losing context
const user = {
name: "Anna",
greet() {
return `Hello, I am ${this.name}`;
}
};
const greetFunction = user.greet;
console.log(greetFunction()); // "Hello, I am undefined"
// ✅ Good: preserving context
const boundGreet = user.greet.bind(user);
console.log(boundGreet()); // "Hello, I am Anna"
// ✅ Or use arrow functions
const user2 = {
name: "Peter",
greet: () => `Hello, I am ${this.name}`, // this doesn't work!
greetCorrect() {
return `Hello, I am ${this.name}`; // this works
}
};
// ❌ Bad: modifying input data
function addItem(array, item) {
array.push(item); // Modifies original array!
return array;
}
function updateUser(user, newData) {
user.name = newData.name; // Modifies original object!
return user;
}
// ✅ Good: creating new data
function addItemSafe(array, item) {
return [...array, item];
}
function updateUserSafe(user, newData) {
return { ...user, ...newData };
}
// ✅ Modern approach
function createUser({ name, email, age = 18, role = 'user' }) {
return {
name,
email,
age,
role,
id: generateId()
};
}
// Usage
const user = createUser({
name: "Anna",
email: "anna@example.com",
age: 25
});
// Rest: collect arguments into array
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Spread: expand array into arguments
function multiply(a, b, c) {
return a * b * c;
}
const numbers = [2, 3, 4];
console.log(multiply(...numbers)); // 24
function fetchData(url, options = {}) {
const config = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
...options // Override defaults
};
return fetch(url, config);
}
// Pure function is easy to test
function calculateTax(amount, rate) {
return amount * rate;
}
// Simple tests
console.assert(calculateTax(100, 0.1) === 10);
console.assert(calculateTax(0, 0.1) === 0);
console.assert(calculateTax(100, 0) === 0);
// Function with side effects is harder to test
function saveAndNotify(data) {
database.save(data); // Need to mock database
emailService.send(data.email); // Need to mock email service
logger.log('Data saved'); // Need to mock logger
}
// Function to test
function validatePassword(password) {
if (password.length < 8) {
return { valid: false, error: 'Password too short' };
}
if (!/[A-Z]/.test(password)) {
return { valid: false, error: 'Need uppercase letter' };
}
if (!/[0-9]/.test(password)) {
return { valid: false, error: 'Need digit' };
}
return { valid: true };
}
// Tests
describe('validatePassword', () => {
test('rejects short passwords', () => {
const result = validatePassword('123');
expect(result.valid).toBe(false);
expect(result.error).toBe('Password too short');
});
test('accepts valid passwords', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(true);
});
});
// ❌ Bad: creating function in loop
for (let i = 0; i < 1000; i++) {
setTimeout(function() {
console.log(i); // Creates 1000 functions
}, i * 100);
}
// ✅ Good: reusing function
function logNumber(number) {
console.log(number);
}
for (let i = 0; i < 1000; i++) {
setTimeout(() => logNumber(i), i * 100);
}
// ✅ Memoization for expensive calculations
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;
};
}
const expensiveCalculation = memoize(function(n) {
console.log('Calculating...');
return n * n * n;
});
console.log(expensiveCalculation(5)); // Calculating... 125
console.log(expensiveCalculation(5)); // 125 (from cache)
Remember: functions are not just a way to group code, they are a powerful tool for creating quality, maintainable applications. Learning the principles of working with functions is the foundation of professional JavaScript programming.
Want more articles on interview preparation?
FollowEasyAdvice, bookmark the site, and level up every day 💪