Functions Basics

45 min read Beginner

Master the fundamental building blocks of reusable code. Learn how to create, call, and work with functions in JavaScript.

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.

JavaScript
// 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:

JavaScript
// 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).

JavaScript
// 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.

JavaScript
// 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:

JavaScript
// 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.

JavaScript
//          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:

JavaScript
// 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:

JavaScript
// 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.

JavaScript
// 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:

JavaScript
// 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.

JavaScript
// 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.

JavaScript
// 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.

IIFE Patterns
// 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:

Naming Conventions
// 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 vs Impure Functions
// ✅ 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.

JSDoc Examples
/**
 * 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