Functions in JavaScript
Functions are reusable blocks of code that perform specific tasks. They are fundamental to organizing, structuring, and reusing code effectively.
📝 Function Declaration
// Basic function declaration
function greet() {
console.log('Hello, World!');
}
greet(); // Call the function
// Function with parameters
function greetPerson(name) {
console.log(`Hello, ${name}!`);
}
greetPerson('John'); // 'Hello, John!'
greetPerson('Jane'); // 'Hello, Jane!'
// Multiple parameters
function add(a, b) {
return a + b;
}
let sum = add(5, 3);
console.log(sum); // 8
// Return value
function multiply(x, y) {
return x * y;
console.log('This never runs'); // Code after return is unreachable
}
let product = multiply(4, 5);
console.log(product); // 20
// Function without return
function sayHello() {
console.log('Hello!');
// Implicit return undefined
}
let result = sayHello();
console.log(result); // undefined
🎭 Function Expressions
// Function expression
const subtract = function(a, b) {
return a - b;
};
console.log(subtract(10, 3)); // 7
// Named function expression
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Can use name inside
};
console.log(factorial(5)); // 120
// Difference from declaration
greetDeclaration(); // Works! (hoisted)
function greetDeclaration() {
console.log('I am hoisted');
}
// greetExpression(); // Error! Not hoisted
const greetExpression = function() {
console.log('I am not hoisted');
};
greetExpression(); // Works here
➡️ Arrow Functions (ES6)
// Arrow function syntax
const square = (x) => {
return x * x;
};
// Concise syntax (implicit return)
const square2 = x => x * x;
// No parameters
const greet = () => console.log('Hello!');
// Multiple parameters
const add = (a, b) => a + b;
// Returning object literal
const makePerson = (name, age) => ({
name: name,
age: age
});
// Or with shorthand
const makePerson2 = (name, age) => ({ name, age });
// Multi-line function body
const isEven = num => {
if (num % 2 === 0) {
return true;
}
return false;
};
// Examples
console.log(square2(5)); // 25
console.log(add(3, 4)); // 7
console.log(makePerson('John', 30)); // { name: 'John', age: 30 }
// Array methods with arrow functions
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);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens); // [2, 4]
console.log(sum); // 15
📦 Parameters and Arguments
Default Parameters
// ES6 default parameters
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
console.log(greet()); // 'Hello, Guest!'
console.log(greet('John')); // 'Hello, John!'
console.log(greet('Jane', 'Hi')); // 'Hi, Jane!'
// Default with expressions
function createUser(name, role = 'user', id = Date.now()) {
return { name, role, id };
}
// Default can reference other parameters
function calculatePrice(price, tax = price * 0.1) {
return price + tax;
}
console.log(calculatePrice(100)); // 110 (100 + 10)
Rest Parameters
// Collect remaining arguments into array
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest must be last parameter
function greetAll(greeting, ...names) {
return names.map(name => `${greeting}, ${name}!`);
}
console.log(greetAll('Hello', 'John', 'Jane', 'Bob'));
// ['Hello, John!', 'Hello, Jane!', 'Hello, Bob!']
// Practical example
function max(...nums) {
if (nums.length === 0) return -Infinity;
return Math.max(...nums);
}
console.log(max(3, 7, 2, 9, 1)); // 9
Arguments Object (Legacy)
// arguments object (array-like, not available in arrow functions)
function oldSum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(oldSum(1, 2, 3, 4)); // 10
// Convert to real array
function toArray() {
return Array.from(arguments);
// Or: return [...arguments];
}
console.log(toArray(1, 2, 3)); // [1, 2, 3]
// Note: Use rest parameters instead in modern code
🔄 Return Values
// Single return value
function divide(a, b) {
if (b === 0) {
return 'Cannot divide by zero';
}
return a / b;
}
// Multiple returns (early return pattern)
function getGrade(score) {
if (score < 0 || score > 100) {
return 'Invalid score';
}
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}
// Return object
function getUser(id) {
return {
id: id,
name: 'John',
email: 'john@example.com'
};
}
// Return array
function getCoordinates() {
return [40.7128, -74.0060];
}
// Destructure return values
const [lat, lng] = getCoordinates();
// Return function (higher-order function)
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
🎯 Scope and Closures
Function Scope
// Variables declared in function are local
function myFunction() {
let localVar = 'I am local';
console.log(localVar); // Works
}
myFunction();
// console.log(localVar); // Error: localVar is not defined
// Nested scope
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(outerVar); // Can access outer
console.log(innerVar); // Can access inner
}
inner();
// console.log(innerVar); // Error: can't access inner
}
outer();
Closures
// Function remembers outer scope
function createCounter() {
let count = 0; // Private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
console.log(counter.decrement()); // 1
// Practical: Private data
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
return 'Invalid amount';
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
return 'Invalid amount or insufficient funds';
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(1000);
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
console.log(account.getBalance()); // 1300
// console.log(balance); // Error: balance is private
🔝 Higher-Order Functions
// Functions that take or return functions
// Take function as argument
function operate(a, b, operation) {
return operation(a, b);
}
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
console.log(operate(5, 3, add)); // 8
console.log(operate(5, 3, multiply)); // 15
// Return function
function greeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = greeter('Hello');
const sayHi = greeter('Hi');
console.log(sayHello('John')); // 'Hello, John!'
console.log(sayHi('Jane')); // 'Hi, Jane!'
// Common higher-order functions
const numbers = [1, 2, 3, 4, 5];
// map - transform each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - keep elements that pass test
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce - combine elements into single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// forEach - execute function for each element
numbers.forEach(n => console.log(n * n));
// find - get first element that passes test
const firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven); // 2
// some - test if any element passes
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true
// every - test if all elements pass
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true
🎨 Function Methods
call, apply, bind
// call - invoke function with specific this
function greet() {
return `Hello, ${this.name}!`;
}
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
console.log(greet.call(person1)); // 'Hello, John!'
console.log(greet.call(person2)); // 'Hello, Jane!'
// call with arguments
function introduce(city, country) {
return `${this.name} from ${city}, ${country}`;
}
console.log(introduce.call(person1, 'New York', 'USA'));
// 'John from New York, USA'
// apply - same as call but array of arguments
console.log(introduce.apply(person1, ['New York', 'USA']));
// bind - create new function with bound this
const greetJohn = greet.bind(person1);
console.log(greetJohn()); // 'Hello, John!'
// Partial application with bind
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
🔁 Recursion
// Function that calls itself
// Factorial
function factorial(n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
// Fibonacci
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(7)); // 13
// Countdown
function countdown(num) {
if (num <= 0) {
console.log('Done!');
return;
}
console.log(num);
countdown(num - 1);
}
countdown(5); // 5, 4, 3, 2, 1, Done!
// Sum array recursively
function sumArray(arr) {
if (arr.length === 0) return 0;
return arr[0] + sumArray(arr.slice(1));
}
console.log(sumArray([1, 2, 3, 4, 5])); // 15
// Flatten nested array
function flatten(arr) {
let result = [];
for (let item of arr) {
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
}
return result;
}
console.log(flatten([1, [2, [3, 4], 5], 6]));
// [1, 2, 3, 4, 5, 6]
💡 Practical Examples
Utility Functions
// Debounce - delay execution until user stops action
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
// Usage: search as user types
const search = debounce((query) => {
console.log(`Searching for: ${query}`);
}, 500);
// Throttle - limit execution frequency
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Memoization - cache results
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const slowFib = memoize(function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
console.log(slowFib(40)); // Fast due to caching
Function Composition
// Compose multiple functions
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = compose(square, double, addOne);
console.log(transform(5)); // 144 ((5+1)*2)^2
// Pipe (left to right)
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const transform2 = pipe(addOne, double, square);
console.log(transform2(5)); // 144
// Practical: Data transformation
const users = [
{ name: 'john doe', age: 25, active: true },
{ name: 'jane smith', age: 30, active: false }
];
const capitalize = str => str.split(' ').map(w =>
w.charAt(0).toUpperCase() + w.slice(1)
).join(' ');
const processUser = pipe(
user => ({ ...user, name: capitalize(user.name) }),
user => ({ ...user, status: user.active ? 'Active' : 'Inactive' })
);
console.log(users.map(processUser));
Currying
// Transform function with multiple args into sequence of functions
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return function(...nextArgs) {
return curried(...args, ...nextArgs);
};
};
}
// Example function
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
// Practical: Reusable configurations
const multiply = curry((a, b, c) => a * b * c);
const multiplyByTwo = multiply(2);
const multiplyByTwoAndThree = multiplyByTwo(3);
console.log(multiplyByTwoAndThree(4)); // 24 (2*3*4)
🎯 Key Takeaways
- Declaration: function name() {} is hoisted, can call before definition
- Expression: const name = function() {} not hoisted, must define first
- Arrow functions: Concise syntax, implicit return, different 'this' binding
- Default parameters: Provide default values for missing arguments
- Rest parameters: ...args collects remaining arguments into array
- Closures: Functions remember their outer scope, enables private variables
- Higher-order: Functions can take and return other functions
- Recursion: Function calls itself, useful for tree-like structures and iteration