JavaScript Functions

Learn how to create and use functions to organize your code into reusable, modular pieces

Function Basics
Understanding the fundamentals of JavaScript functions

What are Functions?

Functions are one of the fundamental building blocks in JavaScript. A function is a reusable block of code designed to perform a particular task. Functions help organize your code, make it reusable, and keep your program modular.

Function Declaration

The most common way to define a function is with a function declaration, which consists of the function keyword, followed by:

  • The name of the function
  • A list of parameters enclosed in parentheses (optional)
  • The function body enclosed in curly braces
javascript
1// Basic function declaration
2function greet() {
3 console.log("Hello, world!");
4}
5
6// Function with parameters
7function greetPerson(name) {
8 console.log("Hello, " + name + "!");
9}
10
11// Function with return value
12function sum(a, b) {
13 return a + b;
14}
15
16// Calling functions
17greet(); // Output: Hello, world!
18greetPerson("Alice"); // Output: Hello, Alice!
19let result = sum(5, 3); // result = 8
20console.log(result); // Output: 8

Function Expressions

Another way to define a function is with a function expression. In this case, the function can be anonymous (without a name) or it can have a name.

javascript
1// Anonymous function expression
2const greet = function() {
3 console.log("Hello, world!");
4};
5
6// Named function expression
7const sum = function addNumbers(a, b) {
8 return a + b;
9};
10
11// Calling function expressions
12greet(); // Output: Hello, world!
13console.log(sum(5, 3)); // Output: 8
14
15// The function name in a named function expression is only visible inside the function
16// console.log(addNumbers); // Error: addNumbers is not defined

Key Difference

Function declarations are hoisted (moved to the top of their scope), which means you can call them before they appear in your code. Function expressions are not hoisted in the same way.

Arrow Functions
Learn about the modern, concise syntax for writing functions

Arrow functions were introduced in ES6 (ECMAScript 2015) and provide a more concise syntax for writing functions. They are especially useful for short, simple functions and for functions that need to preserve the lexical this value.

javascript
1// Basic arrow function
2const greet = () => {
3 console.log("Hello, world!");
4};
5
6// Arrow function with one parameter (parentheses optional)
7const greetPerson = name => {
8 console.log("Hello, " + name + "!");
9};
10
11// Arrow function with multiple parameters
12const sum = (a, b) => {
13 return a + b;
14};
15
16// Arrow function with implicit return (no curly braces)
17const multiply = (a, b) => a * b;
18
19// Calling arrow functions
20greet(); // Output: Hello, world!
21greetPerson("Bob"); // Output: Hello, Bob!
22console.log(sum(5, 3)); // Output: 8
23console.log(multiply(4, 2)); // Output: 8

Arrow Functions and Lexical this

One of the main advantages of arrow functions is that they don't have their own this binding. Instead, they inherit this from the surrounding code (lexical scoping).

javascript
1// Regular function vs Arrow function with 'this'
2
3// With regular function
4const person = {
5 name: "Alice",
6 regularFunction: function() {
7 console.log(this.name); // 'this' refers to the person object
8 },
9 arrowFunction: () => {
10 console.log(this.name); // 'this' refers to the outer scope (likely window or undefined)
11 },
12 delayedRegular: function() {
13 setTimeout(function() {
14 console.log(this.name); // 'this' is lost, refers to window or undefined
15 }, 100);
16 },
17 delayedArrow: function() {
18 setTimeout(() => {
19 console.log(this.name); // 'this' is preserved, refers to person
20 }, 100);
21 }
22};
23
24person.regularFunction(); // Output: Alice
25person.arrowFunction(); // Output: undefined (or depends on global context)
26person.delayedRegular(); // Output: undefined (or depends on global context)
27person.delayedArrow(); // Output: Alice

When to Use Arrow Functions

Use arrow functions when you want to preserve the lexical this, such as in callbacks or event handlers. Avoid using arrow functions for object methods, constructors, or when you need access to arguments object.

Parameters and Arguments
Learn how to pass data to functions and handle parameters

Basic Parameters

Parameters are the names listed in the function definition, while arguments are the actual values passed to the function when it is called.

javascript
1// Function with two parameters
2function add(x, y) {
3 return x + y;
4}
5
6// Calling with two arguments
7console.log(add(5, 3)); // Output: 8
8
9// What happens with missing arguments?
10console.log(add(5)); // Output: NaN (5 + undefined = NaN)
11
12// What happens with extra arguments?
13console.log(add(5, 3, 2)); // Output: 8 (extra arguments are ignored)

Default Parameters

ES6 introduced default parameters, which allow you to specify default values for parameters if no argument is provided or if undefined is passed.

javascript
1// Function with default parameters
2function greet(name = "Guest", greeting = "Hello") {
3 return `${greeting}, ${name}!`;
4}
5
6console.log(greet()); // Output: Hello, Guest!
7console.log(greet("Alice")); // Output: Hello, Alice!
8console.log(greet("Bob", "Hi")); // Output: Hi, Bob!
9console.log(greet(undefined, "Hey")); // Output: Hey, Guest!
10
11// Default parameters can use previous parameters
12function createUser(name, role = "user", id = generateId(name)) {
13 function generateId(name) {
14 return name.toLowerCase().replace(/\s/g, "") + Date.now();
15 }
16 return { name, role, id };
17}
18
19console.log(createUser("John Doe"));
20// Output: { name: "John Doe", role: "user", id: "johndoe1234567890" }

Rest Parameters

The rest parameter syntax allows a function to accept an indefinite number of arguments as an array. It's denoted by three dots (...) followed by a parameter name.

javascript
1// Function with rest parameter
2function sum(...numbers) {
3 return numbers.reduce((total, num) => total + num, 0);
4}
5
6console.log(sum(1, 2)); // Output: 3
7console.log(sum(1, 2, 3, 4, 5)); // Output: 15
8console.log(sum()); // Output: 0
9
10// Rest parameter must be the last parameter
11function logDetails(name, ...details) {
12 console.log(`Name: ${name}`);
13 console.log(`Details: ${details.join(", ")}`);
14}
15
16logDetails("Alice", "Developer", "New York", "30");
17// Output:
18// Name: Alice
19// Details: Developer, New York, 30

The Arguments Object

In non-arrow functions, you can access all arguments passed to a function using the arguments object, which is an array-like object containing all arguments.

javascript
1// Using the arguments object
2function logAll() {
3 for (let i = 0; i < arguments.length; i++) {
4 console.log(`Argument ${i}: ${arguments[i]}`);
5 }
6}
7
8logAll("a", "b", "c");
9// Output:
10// Argument 0: a
11// Argument 1: b
12// Argument 2: c
13
14// Note: arguments is not available in arrow functions
15const arrowLogAll = () => {
16 console.log(arguments); // Error or unexpected behavior
17};
18
19// Use rest parameters instead for arrow functions
20const betterArrowLogAll = (...args) => {
21 args.forEach((arg, i) => {
22 console.log(`Argument ${i}: ${arg}`);
23 });
24};

Parameter Destructuring

You can use object and array destructuring in function parameters to extract values from objects and arrays passed as arguments.

javascript
1// Object destructuring in parameters
2function displayPerson({ name, age, job = "Unknown" }) {
3 console.log(`Name: ${name}, Age: ${age}, Job: ${job}`);
4}
5
6const person = { name: "Alice", age: 30, location: "New York" };
7displayPerson(person);
8// Output: Name: Alice, Age: 30, Job: Unknown
9
10// Array destructuring in parameters
11function displayCoordinates([x, y, z = 0]) {
12 console.log(`X: ${x}, Y: ${y}, Z: ${z}`);
13}
14
15displayCoordinates([10, 20]);
16// Output: X: 10, Y: 20, Z: 0
Scope and Closures
Understand variable scope and the powerful closure concept

Variable Scope

Scope determines the accessibility of variables in your code. JavaScript has three types of scope: global scope, function scope, and block scope (introduced with let and const in ES6).

javascript
1// Global scope
2let globalVar = "I'm global";
3
4function exampleFunction() {
5 // Function scope
6 let functionVar = "I'm function-scoped";
7
8 console.log(globalVar); // Accessible: Output: I'm global
9 console.log(functionVar); // Accessible: Output: I'm function-scoped
10
11 if (true) {
12 // Block scope
13 let blockVar = "I'm block-scoped";
14 var functionScopedVar = "I'm function-scoped too";
15
16 console.log(blockVar); // Accessible: Output: I'm block-scoped
17 }
18
19 // console.log(blockVar); // Error: blockVar is not defined
20 console.log(functionScopedVar); // Accessible: Output: I'm function-scoped too
21}
22
23exampleFunction();
24console.log(globalVar); // Accessible: Output: I'm global
25// console.log(functionVar); // Error: functionVar is not defined

Scope Rules

var declarations are function-scoped (visible throughout the function).
let and const declarations are block-scoped (visible only within the block).
Variables declared without var, let, or const become global variables (not recommended).

Closures

A closure is a function that has access to variables from its outer (enclosing) function's scope, even after the outer function has returned. Closures are one of the most powerful features in JavaScript.

javascript
1// Basic closure example
2function createGreeter(greeting) {
3 // The inner function is a closure that "closes over" the greeting variable
4 return function(name) {
5 return `${greeting}, ${name}!`;
6 };
7}
8
9const sayHello = createGreeter("Hello");
10const sayHi = createGreeter("Hi");
11
12console.log(sayHello("Alice")); // Output: Hello, Alice!
13console.log(sayHi("Bob")); // Output: Hi, Bob!
14
15// Practical closure example: counter
16function createCounter() {
17 let count = 0; // Private variable
18
19 return {
20 increment() {
21 count++;
22 return count;
23 },
24 decrement() {
25 count--;
26 return count;
27 },
28 getCount() {
29 return count;
30 }
31 };
32}
33
34const counter = createCounter();
35console.log(counter.increment()); // Output: 1
36console.log(counter.increment()); // Output: 2
37console.log(counter.getCount()); // Output: 2
38console.log(counter.decrement()); // Output: 1
39
40// The count variable is not directly accessible
41// console.log(counter.count); // Output: undefined

Common Closure Use Cases

javascript
1// Data privacy / encapsulation
2function createBankAccount(initialBalance) {
3 let balance = initialBalance;
4
5 return {
6 deposit(amount) {
7 if (amount > 0) {
8 balance += amount;
9 return `Deposited ${amount}. New balance: ${balance}`;
10 }
11 return "Invalid deposit amount";
12 },
13 withdraw(amount) {
14 if (amount > 0 && amount <= balance) {
15 balance -= amount;
16 return `Withdrew ${amount}. New balance: ${balance}`;
17 }
18 return "Invalid withdrawal amount";
19 },
20 getBalance() {
21 return `Current balance: ${balance}`;
22 }
23 };
24}
25
26const account = createBankAccount(100);
27console.log(account.getBalance()); // Output: Current balance: 100
28console.log(account.deposit(50)); // Output: Deposited 50. New balance: 150
29console.log(account.withdraw(30)); // Output: Withdrew 30. New balance: 120
30console.log(account.withdraw(200)); // Output: Invalid withdrawal amount
31
32// Function factory
33function createMultiplier(factor) {
34 return function(number) {
35 return number * factor;
36 };
37}
38
39const double = createMultiplier(2);
40const triple = createMultiplier(3);
41
42console.log(double(5)); // Output: 10
43console.log(triple(5)); // Output: 15

Closure Gotchas

Be careful with closures in loops. Variables captured by closures refer to their final values, not the value at the time the closure was created. Use let instead of var in loops, or create a new scope for each iteration.

Advanced Function Concepts
Explore higher-order functions, IIFE, and more

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or return functions as their result. They are a powerful concept in functional programming.

javascript
1// Function that takes a function as an argument
2function executeOperation(operation, a, b) {
3 return operation(a, b);
4}
5
6// Functions to pass as arguments
7function add(x, y) {
8 return x + y;
9}
10
11function multiply(x, y) {
12 return x * y;
13}
14
15console.log(executeOperation(add, 5, 3)); // Output: 8
16console.log(executeOperation(multiply, 5, 3)); // Output: 15
17
18// Using arrow functions
19console.log(executeOperation((x, y) => x - y, 5, 3)); // Output: 2
20
21// Array methods that are higher-order functions
22const numbers = [1, 2, 3, 4, 5];
23
24// map: transforms each element
25const doubled = numbers.map(num => num * 2);
26console.log(doubled); // Output: [2, 4, 6, 8, 10]
27
28// filter: selects elements that match a condition
29const evenNumbers = numbers.filter(num => num % 2 === 0);
30console.log(evenNumbers); // Output: [2, 4]
31
32// reduce: accumulates values
33const sum = numbers.reduce((total, num) => total + num, 0);
34console.log(sum); // Output: 15

Immediately Invoked Function Expressions (IIFE)

An IIFE is a function that is executed immediately after it is created. It's a common pattern used to create a private scope for variables.

javascript
1// Basic IIFE syntax
2(function() {
3 console.log("This function is executed immediately");
4})();
5
6// IIFE with parameters
7(function(name) {
8 console.log("Hello, " + name);
9})("Alice");
10
11// IIFE with arrow function
12(() => {
13 console.log("Arrow function IIFE");
14})();
15
16// IIFE to create private scope
17const counter = (function() {
18 let count = 0; // Private variable
19
20 return {
21 increment() {
22 return ++count;
23 },
24 decrement() {
25 return --count;
26 },
27 value() {
28 return count;
29 }
30 };
31})();
32
33console.log(counter.increment()); // Output: 1
34console.log(counter.increment()); // Output: 2
35console.log(counter.value()); // Output: 2

Function Binding and this

JavaScript provides methods to control what this refers to when a function is called:bind(), call(), and apply().

javascript
1// The 'this' problem
2const person = {
3 name: "Alice",
4 greet() {
5 console.log(`Hello, my name is ${this.name}`);
6 }
7};
8
9person.greet(); // Output: Hello, my name is Alice
10
11// Losing 'this' context
12const greetFunction = person.greet;
13// greetFunction(); // Output: Hello, my name is undefined
14
15// Solution 1: bind()
16const boundGreet = person.greet.bind(person);
17boundGreet(); // Output: Hello, my name is Alice
18
19// bind() with arguments
20function multiply(a, b) {
21 return a * b;
22}
23
24const double = multiply.bind(null, 2);
25console.log(double(5)); // Output: 10
26
27// Solution 2: call()
28greetFunction.call(person); // Output: Hello, my name is Alice
29
30// call() with arguments
31function introduce(greeting) {
32 console.log(`${greeting}, my name is ${this.name}`);
33}
34
35introduce.call(person, "Hi"); // Output: Hi, my name is Alice
36
37// Solution 3: apply()
38introduce.apply(person, ["Hello"]); // Output: Hello, my name is Alice

Function Composition and Currying

Function composition and currying are advanced functional programming techniques that help create more modular and reusable code.

javascript
1// Function composition
2function addOne(x) {
3 return x + 1;
4}
5
6function double(x) {
7 return x * 2;
8}
9
10// Manual composition
11function addOneThenDouble(x) {
12 return double(addOne(x));
13}
14
15console.log(addOneThenDouble(3)); // Output: 8
16
17// Composition helper
18function compose(f, g) {
19 return function(x) {
20 return f(g(x));
21 };
22}
23
24const addOneThenDouble2 = compose(double, addOne);
25console.log(addOneThenDouble2(3)); // Output: 8
26
27// Currying
28function curry(fn) {
29 return function curried(...args) {
30 if (args.length >= fn.length) {
31 return fn.apply(this, args);
32 } else {
33 return function(...moreArgs) {
34 return curried.apply(this, args.concat(moreArgs));
35 };
36 }
37 };
38}
39
40function add(a, b, c) {
41 return a + b + c;
42}
43
44const curriedAdd = curry(add);
45
46console.log(curriedAdd(1)(2)(3)); // Output: 6
47console.log(curriedAdd(1, 2)(3)); // Output: 6
48console.log(curriedAdd(1)(2, 3)); // Output: 6
49console.log(curriedAdd(1, 2, 3)); // Output: 6