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-scoped

Callback 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 defined

Key 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 const or let, never var
  • 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