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.
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.
const square = function(n) {
return n * n;
};
console.log(square(5)); // 25
// ❌ Can't call before declaration
// console.log(square(5)); // ReferenceErrorArrow Function — modern shorthand
Concise syntax. No own this — inherits this from surrounding scope.
// 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)); // 10Part 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
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.
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
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
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
// ❌ 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