Manifest Stack home

How functions work in JS

Part 1: Ways to Define a Function

Function Declaration

The classic syntax. Fully hoisted — can be called before its definition in the file.

javascript
greet("Arjun"); // ✅ Works due to hoisting

function greet(name) {
    return "Hello, " + name + "!";
}

console.log(greet("Priya")); // Hello, Priya!

Function Expression

Assigned to a variable. Not hoisted — must be defined before calling.

javascript
const square = function(n) {
    return n * n;
};

console.log(square(5)); // 25

// ❌ Can't call before declaration
// console.log(square(5)); // ReferenceError

Arrow Function — modern shorthand

Concise syntax. No own this — inherits this from surrounding scope.

javascript
// Full arrow function
const add = (a, b) => {
    return a + b;
};

// Short form (implicit return for single expression)
const add2 = (a, b) => a + b;

// Single parameter — parens optional
const double = n => n * 2;

console.log(add(3, 4));    // 7
console.log(double(5));    // 10

Part 2: Functions are First-Class Objects

In JavaScript, functions are values — just like numbers, strings, and arrays. This means you can store them in variables, pass them to other functions, and return them. This is one of the most powerful features of JavaScript.

Stored in a variable

javascript
const sayHi = function() { return "Hi!"; };

let greetFn = sayHi;  // storing function in another variable
console.log(greetFn()); // Hi!

Passed as an argument — callbacks

Passing a function to another function is called a callback. This is how event listeners, array methods, and async code work.

javascript
function runTwice(fn) {
    fn();
    fn();
}

function sayHello() {
    console.log("Hello!");
}

runTwice(sayHello);
// Hello!
// Hello!

// Real-world callbacks:
[1, 2, 3].forEach(n => console.log(n * 2)); // 2 4 6
[1, 2, 3].map(n => n * n);                  // [1, 4, 9]
[1, 2, 3, 4].filter(n => n % 2 === 0);      // [2, 4]

Returned from a function — higher-order functions

javascript
function multiplier(factor) {
    return function(number) {   // returns a function!
        return number * factor;
    };
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// double and triple each "remember" their own factor
// This is a closure — the inner function captures the outer variable.

Part 3: Closures

A closure is when an inner function remembers the variables from its outer function, even after the outer function has finished running. Closures enable data privacy, memoisation, and the module pattern.

Private counter using closure

javascript
function makeCounter() {
    let count = 0;  // private — can't access from outside

    return {
        increment() { count++; },
        decrement() { count--; },
        value()     { return count; }
    };
}

const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.value()); // 2

console.log(count); // ❌ ReferenceError — count is private!

Common closure bug — var in a loop

javascript
// ❌ Bug: all callbacks share the SAME i (var is function-scoped)
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Output: 3 3 3  (not 0 1 2!)

// ✅ Fix: use let (block-scoped — each iteration gets its own i)
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Output: 0 1 2