Complete Guide to Functions
1. Function Fundamentals
A function is a reusable block of code designed to perform a specific task. Functions are one of the fundamental building blocks in JavaScript and essential to writing maintainable and organized code.
1.1 Function Declaration
function greet(name) {
return "Hello, " + name + "!";
}
// Call the function
console.log(greet("Alice")); // Outputs: Hello, Alice!
1.2 Function Expression
const square = function(number) {
return number * number;
};
console.log(square(5)); // Outputs: 25
1.3 Arrow Functions (ES6)
const add = (a, b) => a + b;
console.log(add(3, 5)); // Outputs: 8
Note: Arrow functions have a more concise syntax and inherit the this
value from their enclosing scope.
1.4 Function Parameters and Arguments
- Parameters: Variables listed in the function definition
- Arguments: Actual values passed to the function when it's called
function introduce(name, age, occupation) {
return `Meet ${name}, who is ${age} years old and works as a ${occupation}.`;
}
console.log(introduce("John", 30, "developer"));
1.5 Default Parameters (ES6)
function greetUser(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
console.log(greetUser());
console.log(greetUser("Jane"));
console.log(greetUser("Bob", "Welcome"));
1.6 Return Statement
The return
statement ends function execution and specifies a value to be returned to the function caller.
function multiply(a, b) {
return a * b; // Function execution stops here
console.log("This code never runs");
}
const result = multiply(4, 5);
console.log(result); // Outputs: 20
If no return statement is specified, or if the return statement has no associated expression, JavaScript implicitly returns undefined
.
2. Advanced Function Concepts
2.1 Function Scope and Closures
A closure is a function that has access to its own scope, the outer function's scope, and the global scope.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
2.2 Higher-Order Functions
Functions that take other functions as arguments or return functions as their results.
// Function that accepts another function as an argument
function applyOperation(x, y, operation) {
return operation(x, y);
}
// Different operations we can pass
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
console.log(applyOperation(5, 3, add)); // 8
console.log(applyOperation(5, 3, subtract)); // 2
console.log(applyOperation(5, 3, multiply)); // 15
2.3 Callback Functions
Functions passed to another function to be executed later.
function fetchData(callback) {
// Simulating an API call with setTimeout
setTimeout(function() {
const data = { name: "John", age: 30 };
callback(data);
}, 1000);
}
fetchData(function(userData) {
console.log("Data received:", userData);
});
2.4 Immediately Invoked Function Expressions (IIFE)
Functions that are executed immediately after they are created.
(function() {
let privateVar = "I'm private";
console.log(privateVar);
})();
// console.log(privateVar); // Error: privateVar is not defined
2.5 Rest Parameters
Allows a function to accept an indefinite number of arguments as an array.
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(10, 20)); // 30
2.6 Function Currying
Transforming a function with multiple arguments into a sequence of functions, each with a single argument.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return function(...more) {
return curried(...args, ...more);
};
}
};
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
2.7 Generator Functions
Functions that can be paused and resumed, and yield multiple values.
function* countUpTo(max) {
let count = 1;
while (count <= max) {
yield count++;
}
}
const generator = countUpTo(3);
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().value); // undefined
2.8 Recursive Functions
Functions that call themselves until they reach a base case.
function factorial(n) {
// Base case
if (n <= 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
3. Problem Solving with Functions
3.1 Breaking Down Complex Problems
Functions help us break down complex problems into smaller, manageable parts.
Problem: Calculate Total Price with Tax and Discount
function calculateTax(amount, taxRate) {
return amount * (taxRate / 100);
}
function applyDiscount(amount, discountPercent) {
return amount * (1 - discountPercent / 100);
}
function calculateTotalPrice(basePrice, taxRate, discountPercent) {
const priceAfterDiscount = applyDiscount(basePrice, discountPercent);
const tax = calculateTax(priceAfterDiscount, taxRate);
return priceAfterDiscount + tax;
}
const basePrice = 100;
const taxRate = 8;
const discount = 10;
console.log(`Base price: $${basePrice}`);
console.log(`After ${discount}% discount: $${applyDiscount(basePrice, discount).toFixed(2)}`);
console.log(`Tax (${taxRate}%): $${calculateTax(applyDiscount(basePrice, discount), taxRate).toFixed(2)}`);
console.log(`Final price: $${calculateTotalPrice(basePrice, taxRate, discount).toFixed(2)}`);
3.2 Common Patterns with Functions
Three powerful higher-order functions for array manipulation:
const students = [
{ name: "Alice", grade: 85, graduate: true },
{ name: "Bob", grade: 75, graduate: false },
{ name: "Charlie", grade: 90, graduate: true },
{ name: "David", grade: 65, graduate: false },
{ name: "Eve", grade: 88, graduate: true }
];
// FILTER: Get only graduate students
const graduates = students.filter(student => student.graduate);
// MAP: Extract just the grades of graduates
const graduateGrades = graduates.map(student => student.grade);
// REDUCE: Calculate the average grade
const average = graduateGrades.reduce((sum, grade) => sum + grade, 0) / graduateGrades.length;
console.log("Graduate students:", graduates);
console.log("Their grades:", graduateGrades);
console.log("Average grade:", average.toFixed(2));
Optimization technique that stores expensive function call results and returns the cached result when the same inputs occur again.
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log(`Returning cached result for ${key}`);
return cache[key];
}
console.log(`Computing result for ${key}`);
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Expensive calculation function
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Memoized version
const memoizedFib = memoize(function(n) {
if (n <= 1) return n;
return memoizedFib(n - 1) + memoizedFib(n - 2);
});
console.log(memoizedFib(10)); // First calculation
console.log(memoizedFib(10)); // Cached result
Functions that create and return objects with specific properties and methods.
function createUser(name, age, role) {
return {
name,
age,
role,
isAdmin() {
return this.role === 'admin';
},
describe() {
return `${this.name} is ${this.age} years old and has role: ${this.role}`;
}
};
}
const user1 = createUser('Alice', 28, 'admin');
const user2 = createUser('Bob', 35, 'user');
console.log(user1.describe());
console.log(`Is Alice an admin? ${user1.isAdmin()}`);
console.log(user2.describe());
console.log(`Is Bob an admin? ${user2.isAdmin()}`);
3.3 Real-World Examples
// Sample data: Sales records
const salesData = [
{ date: '2023-01-15', product: 'Laptop', price: 1200, quantity: 3 },
{ date: '2023-01-16', product: 'Phone', price: 800, quantity: 5 },
{ date: '2023-01-16', product: 'Tablet', price: 500, quantity: 2 },
{ date: '2023-01-17', product: 'Laptop', price: 1200, quantity: 1 },
{ date: '2023-01-18', product: 'Phone', price: 800, quantity: 4 },
];
// Step 1: Calculate total for each sale
function addTotalToSales(sales) {
return sales.map(sale => ({
...sale,
total: sale.price * sale.quantity
}));
}
// Step 2: Group by product
function groupByProduct(sales) {
return sales.reduce((grouped, sale) => {
if (!grouped[sale.product]) {
grouped[sale.product] = [];
}
grouped[sale.product].push(sale);
return grouped;
}, {});
}
// Step 3: Calculate summary for each product
function summarizeByProduct(groupedSales) {
const summary = [];
for (const product in groupedSales) {
const productSales = groupedSales[product];
summary.push({
product,
totalSold: productSales.reduce((sum, sale) => sum + sale.quantity, 0),
totalRevenue: productSales.reduce((sum, sale) => sum + sale.total, 0),
averagePrice: productSales[0].price
});
}
return summary;
}
// Execute the pipeline
const salesWithTotal = addTotalToSales(salesData);
const groupedSales = groupByProduct(salesWithTotal);
const productSummary = summarizeByProduct(groupedSales);
console.log(JSON.stringify(productSummary, null, 2));
A common technique to improve performance by limiting how often a function can be called.
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Simulating multiple rapid events
function handleSearch(query) {
console.log(`Searching for: ${query}`);
}
const debouncedSearch = debounce(handleSearch, 300);
// Simulate user typing "javascript" quickly
console.log("User types 'j'");
debouncedSearch('j');
setTimeout(() => {
console.log("User types 'ja'");
debouncedSearch('ja');
}, 100);
setTimeout(() => {
console.log("User types 'jav'");
debouncedSearch('jav');
}, 200);
setTimeout(() => {
console.log("User types 'java'");
debouncedSearch('java');
}, 250);
setTimeout(() => {
console.log("User types 'javas'");
debouncedSearch('javas');
}, 300);
setTimeout(() => {
console.log("User types 'javasc'");
debouncedSearch('javasc');
}, 350);
setTimeout(() => {
console.log("User types 'javascr'");
debouncedSearch('javascr');
}, 400);
setTimeout(() => {
console.log("User types 'javascri'");
debouncedSearch('javascri');
}, 450);
setTimeout(() => {
console.log("User types 'javascrip'");
debouncedSearch('javascrip');
}, 500);
setTimeout(() => {
console.log("User types 'javascript'");
debouncedSearch('javascript');
}, 550);
// The search will only happen once, 300ms after the last call
3.4 Common Problem-Solving Techniques
Technique | Description | Use Case |
---|---|---|
Decomposition | Breaking a complex problem into smaller, manageable parts | Multi-step calculations or data processing pipelines |
Recursion | Solving a problem by solving smaller instances of the same problem | Tree traversal, factorial calculation, Fibonacci sequence |
Memoization | Storing results of expensive function calls | Optimization for recursive functions or API calls |
Composition | Combining simple functions to build more complex ones | Data transformation, functional programming |
Iteration | Repeatedly applying a process to solve a problem | Array processing, looping over collections |
Function Composition Example
// Simple functions
const double = x => x * 2;
const increment = x => x + 1;
const square = x => x * x;
// Compose function
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
// Create new function by composition
const doubleThenIncrementThenSquare = compose(square, increment, double);
console.log(doubleThenIncrementThenSquare(3)); // ((3*2)+1)² = 49