Functions are the primary building blocks of any JavaScript program. They let you wrap up a block of code, name it, and reuse it. Understanding scope — where variables are accessible — is equally important.
Function Declarations
A function declaration defines a named function that is hoisted to the top of its scope.
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet("Sabaoon")); // "Hello, Sabaoon!"
Because declarations are hoisted, you can call them before they appear in your code:
console.log(add(2, 3)); // 5 — works due to hoisting
function add(a, b) {
return a + b;
}Function Expressions
A function expression assigns a function to a variable. It is not hoisted.
const multiply = function (a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
Arrow Functions
Arrow functions provide a shorter syntax and do not have their own this binding.
const square = (n) => n * n;
const sum = (a, b) => a + b;
// Multi-line arrow function
const formatUser = (user) => {
const { name, age } = user;
return `${name} is ${age} years old`;
};
console.log(square(4)); // 16
console.log(sum(3, 7)); // 10
console.log(formatUser({ name: "Alex", age: 28 })); // "Alex is 28 years old"
When to Use Each
| Style | Hoisted | Own this | Best For |
|---|---|---|---|
| Declaration | Yes | Yes | Top-level named functions |
| Expression | No | Yes | Callbacks, conditionals |
| Arrow | No | No | Short callbacks, array methods |
Default Parameters
You can assign default values to function parameters.
function createUser(name, role = "viewer") {
return { name, role };
}
console.log(createUser("Alex")); // { name: "Alex", role: "viewer" }
console.log(createUser("Alex", "admin")); // { name: "Alex", role: "admin" }
Rest Parameters
The rest operator ... collects remaining arguments into an array.
function total(...numbers) {
return numbers.reduce((sum, n) => sum + n, 0);
}
console.log(total(1, 2, 3, 4)); // 10
Understanding Scope
Scope determines where variables are accessible. JavaScript has three types of scope.
Global Scope
Variables declared outside any function or block are globally accessible.
const appName = "MyApp"; // global
function showApp() {
console.log(appName); // accessible here
}Function Scope
Variables declared with var inside a function are only accessible within that function.
function example() {
var secret = 42;
console.log(secret); // 42
}
// console.log(secret); // ReferenceError
Block Scope
Variables declared with let or const inside a block ({}) are only accessible within that block.
if (true) {
let blockVar = "inside";
const blockConst = "also inside";
console.log(blockVar); // "inside"
}
// console.log(blockVar); // ReferenceError
This is why let and const are preferred over var — they give you more predictable scoping.
Closures
A closure is a function that remembers the variables from its outer scope even after the outer function has returned.
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
},
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
Closures are used everywhere in JavaScript — event handlers, callbacks, module patterns, and React hooks all rely on them.
Higher-Order Functions
A higher-order function either takes a function as an argument or returns a function.
// Takes a function as argument
function applyOperation(a, b, operation) {
return operation(a, b);
}
console.log(applyOperation(10, 5, (a, b) => a + b)); // 15
console.log(applyOperation(10, 5, (a, b) => a * b)); // 50
// Returns a function
function createMultiplier(factor) {
return (number) => number * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Immediately Invoked Function Expressions (IIFE)
An IIFE runs immediately after it is defined. It creates a private scope.
const result = (() => {
const secret = "hidden";
return secret.toUpperCase();
})();
console.log(result); // "HIDDEN"
// console.log(secret); // ReferenceError
IIFEs were essential before ES modules existed. Today they are less common but still useful for one-time initialization.
Practical Exercise
Build a simple task manager using closures:
function createTaskManager() {
const tasks = [];
return {
add(title) {
tasks.push({ title, done: false, id: tasks.length + 1 });
},
complete(id) {
const task = tasks.find((t) => t.id === id);
if (task) task.done = true;
},
list() {
return tasks.map(
(t) => `${t.done ? "[x]" : "[ ]"} ${t.id}. ${t.title}`
);
},
pending() {
return tasks.filter((t) => !t.done).length;
},
};
}
const manager = createTaskManager();
manager.add("Learn JavaScript");
manager.add("Build a project");
manager.complete(1);
console.log(manager.list());
// ["[x] 1. Learn JavaScript", "[ ] 2. Build a project"]
console.log(manager.pending()); // 1
Key Takeaways
- Function declarations are hoisted; expressions and arrow functions are not.
- Arrow functions do not have their own
this— use them for callbacks and array methods. letandconstare block-scoped, giving you tighter control over variable visibility.- Closures let inner functions access outer variables after the outer function has returned.
- Higher-order functions accept or return other functions, enabling powerful composition patterns.