02 - Functions
What are Functions?
Functions are reusable blocks of code that perform a specific task. They help you avoid repetition and organize your code.
// Function declaration
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet("Alice")); // "Hello, Alice!"
console.log(greet("Bob")); // "Hello, Bob!"Function Declaration vs Expression
// Function declaration — hoisted (can call before definition)
function add(a, b) {
return a + b;
}
// Function expression — not hoisted
const subtract = function(a, b) {
return a - b;
};
// Arrow function — modern and concise, NOT hoisted
const multiply = (a, b) => {
return a * b;
};
// Arrow function — implicit return (no braces needed for single expression)
const divide = (a, b) => a / b;Hoisting
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during compilation.
✅ Function Declarations ARE Hoisted
// This works! Function declaration is hoisted
greet("Alice"); // "Hello, Alice!"
function greet(name) {
return `Hello, ${name}!`;
}❌ Function Expressions and Arrow Functions are NOT Hoisted
// ❌ Error: Cannot access 'subtract' before initialization
console.log(subtract(5, 3));
const subtract = function(a, b) {
return a - b;
};
// ❌ Error: Cannot access 'multiply' before initialization
console.log(multiply(4, 2));
const multiply = (a, b) => a * b;Why? Function expressions and arrow functions are assigned to variables declared with const or let, which exist in a "temporal dead zone" before the declaration line.
Rule of Thumb
- Want hoisting? Use function declarations
- Don't need hoisting? Use arrow functions (modern preference)
// ✅ Use function declarations for hoisting
function calculateTotal(price, tax) {
return price + (price * tax);
}
// ✅ Use arrow functions for callbacks and short functions
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);Parameters and Arguments
// Parameters — variables listed in the function definition
function introduce(name, age) {
console.log(`I'm ${name}, ${age} years old`);
}
// Arguments — actual values passed when calling
introduce("Alice", 25);
// Default parameters
function greet(name = "Guest") {
return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, Guest!"
console.log(greet("Alice")); // "Hello, Alice!"Return Values
// Functions can return a value
function square(n) {
return n * n;
}
let result = square(5); // 25
// Without return, function returns undefined
function logMessage(msg) {
console.log(msg);
// no return statement
}
let value = logMessage("Hi"); // undefined
// Early return
function isAdult(age) {
if (age >= 18) {
return true;
}
return false;
}
// Simpler version using direct return
function isAdult(age) {
return age >= 18;
}Arrow Functions
Modern JavaScript prefers arrow functions for their concise syntax. Important: Arrow functions are NOT hoisted.
// Traditional function
function double(n) {
return n * 2;
}
// Arrow function — long form
const double = (n) => {
return n * 2;
};
// Arrow function — short form (implicit return)
const double = (n) => n * 2;
// Single parameter — parentheses optional
const double = n => n * 2;
// No parameters — parentheses required
const greet = () => "Hello!";
// Multiple parameters — parentheses required
const add = (a, b) => a + b;Remember: Arrow functions must be defined before they're used:
// ❌ Error: Cannot access 'greet' before initialization
greet();
const greet = () => "Hello!";
// ✅ Correct: Define first, then use
const greet = () => "Hello!";
greet(); // Works!Scope
Variables have different visibility depending on where they're declared:
// Global scope — accessible everywhere
let global = "I'm global";
function test() {
// Function scope — only accessible inside the function
let local = "I'm local";
console.log(global); // ✅ works
console.log(local); // ✅ works
}
test();
console.log(global); // ✅ works
console.log(local); // ❌ error — local is not defined
// Block scope — let and const are block-scoped
if (true) {
let blockScoped = "I'm in the block";
var functionScoped = "I'm function-scoped";
}
console.log(functionScoped); // ✅ works (var ignores block scope)
console.log(blockScoped); // ❌ error — block-scopedCallback Functions
Functions can be passed as arguments to other functions:
// A function that takes another function as a parameter
function processUser(name, callback) {
console.log(`Processing ${name}...`);
callback(name);
}
// Pass a function as an argument
processUser("Alice", function(name) {
console.log(`Done processing ${name}`);
});
// More common with arrow functions
processUser("Bob", (name) => {
console.log(`Done processing ${name}`);
});
// Real-world example — array methods
const numbers = [1, 2, 3, 4];
numbers.forEach((num) => {
console.log(num * 2);
});Rest Parameters
Collect multiple arguments into an array:
function sum(...numbers) {
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// Combine with regular parameters
function introduce(greeting, ...names) {
console.log(`${greeting} ${names.join(", ")}`);
}
introduce("Hello", "Alice", "Bob", "Charlie");
// "Hello Alice, Bob, Charlie"Immediately Invoked Function Expressions (IIFE)
Run a function immediately after defining it:
(function() {
console.log("I run immediately!");
})();
// With arrow function
(() => {
console.log("Also immediate!");
})();
// Useful for creating isolated scope
(function() {
let private = "Can't access me outside";
console.log(private); // works here
})();
console.log(private); // ❌ error — not definedKey Takeaways
- Functions make code reusable and organized
- Function declarations are hoisted — can be called before definition
- Arrow functions and function expressions are NOT hoisted — must be defined first
- Use arrow functions (
=>) for concise syntax (modern preference) - Always use
constorlet, nevervar - Functions can take other functions as parameters (callbacks)
- Rest parameters (
...) collect arguments into an array - Return values let functions produce results
- Scope determines where variables are accessible