What Are Functions?
A function is a reusable block of code designed to perform a specific task. Functions help you organize code, avoid repetition, and make your programs more modular and maintainable.
Why Use Functions?
- Reusability: Write once, use many times
- Organization: Break complex problems into smaller pieces
- Maintainability: Fix bugs in one place
- Abstraction: Hide complex implementation details
Function Declarations
The most traditional way to create a function is using a function declaration. It starts with the function keyword, followed by a name, parameters, and the function body.
// Basic function declaration
function greet() {
console.log("Hello, World!");
}
// Calling the function
greet(); // Output: Hello, World!
// Function with parameters
function greetPerson(name) {
console.log("Hello, " + name + "!");
}
greetPerson("Alice"); // Output: Hello, Alice!
greetPerson("Bob"); // Output: Hello, Bob!
// Function with multiple parameters
function addNumbers(a, b) {
console.log(a + b);
}
addNumbers(5, 3); // Output: 8
addNumbers(10, 20); // Output: 30
Hoisting
Function declarations are hoisted to the top of their scope. This means you can call a function before it's defined in your code:
// This works due to hoisting!
sayHello(); // Output: Hello!
function sayHello() {
console.log("Hello!");
}
Function Expressions
A function expression creates a function and assigns it to a variable. The function can be named or anonymous (unnamed).
// Anonymous function expression
const greet = function() {
console.log("Hello!");
};
greet(); // Output: Hello!
// Named function expression
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Can call itself by name
};
console.log(factorial(5)); // Output: 120
// Function expression with parameters
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 7)); // Output: 28
No Hoisting!
Unlike function declarations, function expressions are NOT hoisted. You must define them before calling them:
// This will throw an error!
// greet(); // Error: Cannot access 'greet' before initialization
const greet = function() {
console.log("Hello!");
};
greet(); // This works
Arrow Functions
Arrow functions provide a shorter syntax for writing function expressions. Introduced in ES6, they're especially useful for short, simple functions.
// Traditional function expression
const add = function(a, b) {
return a + b;
};
// Arrow function equivalent
const addArrow = (a, b) => {
return a + b;
};
// Concise arrow function (implicit return)
const addConcise = (a, b) => a + b;
console.log(add(2, 3)); // Output: 5
console.log(addArrow(2, 3)); // Output: 5
console.log(addConcise(2, 3)); // Output: 5
// Single parameter (parentheses optional)
const double = x => x * 2;
const square = (x) => x * x;
console.log(double(5)); // Output: 10
console.log(square(4)); // Output: 16
// No parameters (parentheses required)
const sayHi = () => console.log("Hi!");
sayHi(); // Output: Hi!
// Multi-line arrow function
const greetUser = (name) => {
const greeting = "Welcome";
const message = `${greeting}, ${name}!`;
return message;
};
console.log(greetUser("Alice")); // Output: Welcome, Alice!
Returning Objects
When returning an object literal with the concise syntax, wrap it in parentheses:
// Wrong - JavaScript thinks {} is a code block
// const createPerson = (name) => { name: name }; // Returns undefined!
// Correct - wrap object in parentheses
const createPerson = (name, age) => ({ name: name, age: age });
// With shorthand property syntax
const createPersonShort = (name, age) => ({ name, age });
console.log(createPerson("John", 30));
// Output: { name: "John", age: 30 }
Parameters and Arguments
Parameters are variables listed in a function's definition. Arguments are the actual values passed when calling the function.
// parameters
// ↓ ↓
function greet(name, age) {
console.log(`${name} is ${age} years old`);
}
// arguments
// ↓ ↓
greet("Alice", 25); // Output: Alice is 25 years old
Default Parameters
You can set default values for parameters. If no argument is provided, the default is used:
// Default parameter values
function greet(name = "Guest", greeting = "Hello") {
console.log(`${greeting}, ${name}!`);
}
greet(); // Output: Hello, Guest!
greet("Alice"); // Output: Hello, Alice!
greet("Bob", "Welcome"); // Output: Welcome, Bob!
// Default with arrow functions
const multiply = (a, b = 1) => a * b;
console.log(multiply(5)); // Output: 5 (5 * 1)
console.log(multiply(5, 3)); // Output: 15
// Default using expressions
function createDate(year = new Date().getFullYear()) {
return year;
}
console.log(createDate()); // Output: current year
console.log(createDate(2020)); // Output: 2020
// Default using previous parameters
function greetFormal(firstName, lastName, fullName = `${firstName} ${lastName}`) {
console.log(`Welcome, ${fullName}!`);
}
greetFormal("John", "Doe"); // Output: Welcome, John Doe!
Rest Parameters
Rest parameters allow a function to accept an indefinite number of arguments as an array:
// Rest parameter collects all arguments into an array
function sum(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // Output: 3
console.log(sum(1, 2, 3, 4, 5)); // Output: 15
console.log(sum()); // Output: 0
// Combining regular and rest parameters
function introduce(greeting, ...names) {
names.forEach(name => {
console.log(`${greeting}, ${name}!`);
});
}
introduce("Hello", "Alice", "Bob", "Charlie");
// Output:
// Hello, Alice!
// Hello, Bob!
// Hello, Charlie!
// Using with array methods
const average = (...numbers) => {
if (numbers.length === 0) return 0;
const total = numbers.reduce((sum, n) => sum + n, 0);
return total / numbers.length;
};
console.log(average(10, 20, 30)); // Output: 20
Return Values
Functions can return a value using the return statement. Once return executes, the function stops immediately.
// Returning a value
function add(a, b) {
return a + b;
}
const result = add(5, 3);
console.log(result); // Output: 8
// Using return value directly
console.log(add(10, 20)); // Output: 30
// Return stops execution
function checkAge(age) {
if (age < 0) {
return "Invalid age";
}
if (age < 18) {
return "Minor";
}
return "Adult";
// Code after return is never executed
console.log("This never runs");
}
console.log(checkAge(-5)); // Output: Invalid age
console.log(checkAge(15)); // Output: Minor
console.log(checkAge(25)); // Output: Adult
// Functions without return statement return undefined
function noReturn() {
console.log("Hello");
}
const value = noReturn(); // Logs: Hello
console.log(value); // Output: undefined
// Empty return also returns undefined
function earlyExit(x) {
if (x < 0) {
return; // Returns undefined
}
return x * 2;
}
console.log(earlyExit(-5)); // Output: undefined
console.log(earlyExit(5)); // Output: 10
Returning Multiple Values
JavaScript functions can only return one value, but you can return an array or object containing multiple values:
// Return an array
function getMinMax(numbers) {
const min = Math.min(...numbers);
const max = Math.max(...numbers);
return [min, max];
}
const [min, max] = getMinMax([5, 2, 9, 1, 7]);
console.log(min, max); // Output: 1 9
// Return an object
function getUserInfo(id) {
// Simulating fetched data
return {
id: id,
name: "John Doe",
email: "john@example.com"
};
}
const { name, email } = getUserInfo(123);
console.log(name, email); // Output: John Doe john@example.com
// Return object with calculations
function calculate(a, b) {
return {
sum: a + b,
difference: a - b,
product: a * b,
quotient: a / b
};
}
const results = calculate(10, 5);
console.log(results);
// Output: { sum: 15, difference: 5, product: 50, quotient: 2 }
Function Scope
Variables declared inside a function are local to that function and cannot be accessed from outside. This is called function scope.
// Local scope
function greet() {
const message = "Hello!"; // Local variable
console.log(message); // Works fine
}
greet();
// console.log(message); // Error! message is not defined
// Global vs local scope
let globalVar = "I'm global";
function showScopes() {
let localVar = "I'm local";
console.log(globalVar); // Can access global
console.log(localVar); // Can access local
}
showScopes();
console.log(globalVar); // Works
// console.log(localVar); // Error! localVar is not defined
// Variable shadowing
let name = "Global Name";
function shadowExample() {
let name = "Local Name"; // Shadows the global variable
console.log(name); // Output: Local Name
}
shadowExample();
console.log(name); // Output: Global Name (unchanged)
// Nested functions and scope
function outer() {
const outerVar = "outer";
function inner() {
const innerVar = "inner";
console.log(outerVar); // Can access outer's variable
console.log(innerVar); // Can access its own variable
}
inner();
// console.log(innerVar); // Error! Can't access inner's variable
}
outer();
Best Practice
Prefer local variables over global ones. Global variables can be modified from anywhere, making bugs harder to track. Keep your functions self-contained when possible.
Functions as Values
In JavaScript, functions are first-class citizens. This means functions can be assigned to variables, passed as arguments, and returned from other functions.
// Functions stored in variables
const greet = function(name) {
return `Hello, ${name}!`;
};
// Passing functions to other functions (callbacks)
function processUserInput(callback) {
const name = "Alice";
const result = callback(name);
console.log(result);
}
processUserInput(greet); // Output: Hello, Alice!
// Functions in arrays
const operations = [
(x) => x + 1,
(x) => x * 2,
(x) => x ** 2
];
let value = 5;
for (const operation of operations) {
value = operation(value);
}
console.log(value); // ((5 + 1) * 2) ** 2 = 144
// Functions in objects (methods)
const calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => a / b
};
console.log(calculator.add(10, 5)); // Output: 15
console.log(calculator.multiply(4, 3)); // Output: 12
// Returning functions from functions
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15
IIFE (Immediately Invoked Function Expression)
An IIFE is a function that runs as soon as it's defined. It's a pattern used to create private scope and avoid polluting the global namespace.
// Basic IIFE syntax
(function() {
console.log("I run immediately!");
})();
// IIFE with arrow function
(() => {
console.log("Arrow IIFE!");
})();
// IIFE with parameters
(function(name) {
console.log(`Hello, ${name}!`);
})("World");
// IIFE returning a value
const result = (function() {
const privateValue = 42;
return privateValue * 2;
})();
console.log(result); // 84
// Creating private scope (Module Pattern)
const counter = (function() {
let count = 0; // Private variable
return {
increment: function() { count++; },
decrement: function() { count--; },
getCount: function() { return count; }
};
})();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// console.log(count); // Error! count is private
// Avoiding global variable conflicts
(function($, window, undefined) {
// $ is guaranteed to be jQuery here
// window is the global object
// undefined is truly undefined (can't be reassigned)
console.log("Safe scope!");
})(jQuery, window);
// Async IIFE (useful for top-level await alternative)
(async function() {
const data = await fetchSomeData();
console.log(data);
})();
// Or with arrow syntax
(async () => {
const data = await fetchSomeData();
console.log(data);
})();
Modern Alternative
With ES6 modules, IIFEs are less common since modules have their own scope. However, IIFEs are still useful in scripts, browser console, and when you need immediate execution with private scope.
Function Naming Conventions
Good function names make code self-documenting. Follow these conventions used by professional developers:
// 1. Use verb + noun for action functions
function getUserData(userId) { }
function calculateTotalPrice(items) { }
function sendEmailNotification(recipient, message) { }
function validateFormInput(input) { }
// 2. Use "is", "has", "can", "should" for boolean returns
function isValidEmail(email) { return email.includes("@"); }
function hasPermission(user, action) { return user.roles.includes(action); }
function canEdit(document, user) { return document.ownerId === user.id; }
function shouldUpdate(oldData, newData) { return oldData !== newData; }
// 3. Use "get" for retrieving values (no side effects)
function getName() { return this.name; }
function getFormattedDate(date) { return date.toISOString(); }
function getRandomNumber(min, max) { return Math.random() * (max - min) + min; }
// 4. Use "set" for assigning values
function setUserName(name) { this.name = name; }
function setTheme(theme) { document.body.className = theme; }
// 5. Use "create", "make", "build" for factory functions
function createUser(name, email) { return { name, email }; }
function makeRequest(url, options) { return fetch(url, options); }
function buildQueryString(params) { return new URLSearchParams(params); }
// 6. Use "handle" for event handlers
function handleClick(event) { }
function handleFormSubmit(event) { }
function handleKeyPress(event) { }
// 7. Use "on" for callbacks/listeners
function onUserLogin(callback) { }
function onDataReceived(data) { }
function onError(error) { }
// 8. Use "init", "setup" for initialization
function initApp() { }
function setupEventListeners() { }
function initializeDatabase() { }
// 9. Async operations: use "fetch", "load", "save"
async function fetchUserProfile(userId) { }
async function loadConfiguration() { }
async function saveChanges(data) { }
// 10. Transformations: "to", "from", "convert", "parse", "format"
function toUpperCase(str) { return str.toUpperCase(); }
function fromJSON(jsonString) { return JSON.parse(jsonString); }
function convertToArray(iterable) { return Array.from(iterable); }
function parseDate(dateString) { return new Date(dateString); }
function formatCurrency(amount) { return `$${amount.toFixed(2)}`; }
Introduction to Pure Functions
A pure function always returns the same output for the same input and has no side effects. Pure functions are predictable, testable, and easy to reason about.
// ✅ PURE FUNCTION - same input always gives same output
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Always 5
console.log(add(2, 3)); // Always 5
// ✅ PURE - no external dependencies
function calculateArea(width, height) {
return width * height;
}
// ✅ PURE - doesn't modify input
function addItemToArray(arr, item) {
return [...arr, item]; // Returns NEW array
}
const original = [1, 2, 3];
const updated = addItemToArray(original, 4);
console.log(original); // [1, 2, 3] - unchanged!
console.log(updated); // [1, 2, 3, 4]
// ❌ IMPURE - modifies external state
let total = 0;
function addToTotal(amount) {
total += amount; // Side effect: modifies external variable
return total;
}
console.log(addToTotal(10)); // 10
console.log(addToTotal(10)); // 20 - different output for same input!
// ❌ IMPURE - modifies input (mutation)
function addItemBad(arr, item) {
arr.push(item); // Modifies the original array!
return arr;
}
// ❌ IMPURE - depends on external state
let taxRate = 0.1;
function calculateTax(amount) {
return amount * taxRate; // Depends on external taxRate
}
// ❌ IMPURE - has side effects (I/O, DOM, etc.)
function logMessage(message) {
console.log(message); // Side effect: I/O
}
function updateDOM(elementId, content) {
document.getElementById(elementId).textContent = content; // Side effect
}
// ✅ Making impure functions more pure
// Inject dependencies as parameters
function calculateTaxPure(amount, rate) {
return amount * rate;
}
// Return new objects instead of mutating
function updateUser(user, updates) {
return { ...user, ...updates }; // Returns new object
}
Benefits of Pure Functions
- Predictable: Same input always gives same output
- Testable: Easy to test without mocking
- Cacheable: Results can be memoized
- Parallelizable: Safe to run concurrently
Documenting Functions with JSDoc
JSDoc is a standard way to document JavaScript code. It provides type hints, parameter descriptions, and generates documentation.
/**
* Calculates the sum of two numbers.
* @param {number} a - The first number.
* @param {number} b - The second number.
* @returns {number} The sum of a and b.
*/
function add(a, b) {
return a + b;
}
/**
* Greets a user by name.
* @param {string} name - The user's name.
* @param {string} [greeting="Hello"] - Optional greeting phrase.
* @returns {string} The greeting message.
* @example
* greet("Alice"); // "Hello, Alice!"
* greet("Bob", "Welcome"); // "Welcome, Bob!"
*/
function greet(name, greeting = "Hello") {
return `${greeting}, ${name}!`;
}
/**
* Represents a user in the system.
* @typedef {Object} User
* @property {number} id - The user's unique identifier.
* @property {string} name - The user's display name.
* @property {string} email - The user's email address.
* @property {boolean} [isAdmin=false] - Whether the user has admin privileges.
*/
/**
* Fetches a user by their ID.
* @param {number} userId - The ID of the user to fetch.
* @returns {Promise<User>} A promise that resolves to the user object.
* @throws {Error} If the user is not found.
* @async
*/
async function fetchUser(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`User ${userId} not found`);
}
return response.json();
}
/**
* Filters an array based on a predicate function.
* @template T
* @param {T[]} array - The array to filter.
* @param {function(T): boolean} predicate - The filter function.
* @returns {T[]} A new array with elements that pass the test.
*/
function filterArray(array, predicate) {
return array.filter(predicate);
}
/**
* @deprecated Use newFunction() instead.
* @see newFunction
*/
function oldFunction() {
// ...
}
/**
* Configuration options for the API client.
* @typedef {Object} APIConfig
* @property {string} baseUrl - The base URL for API requests.
* @property {number} [timeout=5000] - Request timeout in milliseconds.
* @property {Object.<string, string>} [headers] - Custom headers.
*/
/**
* Creates an API client with the given configuration.
* @param {APIConfig} config - The configuration object.
* @returns {Object} The API client instance.
*/
function createAPIClient(config) {
// Implementation...
}
VS Code Integration
VS Code automatically reads JSDoc comments and provides IntelliSense, type checking, and hover documentation. This gives you TypeScript-like benefits without leaving JavaScript!
Summary
Function Declaration
function name() {} - Traditional, hoisted syntax
Function Expression
const fn = function() {} - Assigned to variable, not hoisted
Arrow Function
const fn = () => {} - Shorter syntax, implicit return
Parameters
Input values with defaults, rest (...args)
Return Values
Output value, stops execution, undefined if omitted
Scope
Local variables are private to the function