Function Declarations
A function is a reusable block of code that performs a specific task. In JavaScript, the most traditional way to define a function is with a function declaration:
// Function declaration
function greet(name) {
console.log(`Hello, ${name}!`);
}
// Calling the function
greet("Alice"); // "Hello, Alice!"
greet("Bob"); // "Hello, Bob!"
// A function that performs a calculation
function add(a, b) {
return a + b;
}
const result = add(3, 5);
console.log(result); // 8
Function declarations are hoisted, which means you can call them before they appear in your code:
// This works — declarations are hoisted
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
}
Function Expressions
A function expression assigns a function to a variable. The function itself can be anonymous (no name) or named:
// Anonymous function expression
const greet = function(name) {
console.log(`Hello, ${name}!`);
};
greet("Alice"); // "Hello, Alice!"
// Named function expression (useful for debugging/recursion)
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(5)); // 120
Unlike declarations, function expressions are not hoisted. You must define them before you use them:
// This will NOT work — expressions are not hoisted
// sayHi(); // TypeError: sayHi is not a function
const sayHi = function() {
console.log("Hi!");
};
sayHi(); // "Hi!" — works after the definition
Arrow Functions
Arrow functions (introduced in ES6) provide a shorter syntax for writing functions. They are especially popular for callbacks and short operations:
// Standard function expression
const add = function(a, b) {
return a + b;
};
// Arrow function equivalent
const addArrow = (a, b) => {
return a + b;
};
// Concise body — if there's just one expression, you can omit {} and return
const addShort = (a, b) => a + b;
// Single parameter — parentheses are optional
const double = n => n * 2;
// No parameters — empty parentheses required
const getRandom = () => Math.random();
// Examples in action
console.log(addShort(3, 5)); // 8
console.log(double(4)); // 8
console.log(getRandom()); // 0.7234... (random)
Arrow functions are not just shorter syntax — they behave differently
with this. Arrow functions do not have their own this
binding; they inherit this from the surrounding scope. This
makes them ideal for callbacks but unsuitable for object methods that
need to reference the object via this. Regular functions
get their own this based on how they are called.
// Arrow functions and this
const counter = {
count: 0,
// Regular function — 'this' refers to counter
increment: function() {
this.count++;
console.log(this.count);
},
// Arrow function — 'this' is inherited (NOT counter!)
// incrementArrow: () => {
// this.count++; // 'this' is the outer scope, not counter
// }
};
counter.increment(); // 1
counter.increment(); // 2
// Where arrow functions shine: callbacks
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
Parameters and Default Values
Functions can accept parameters (inputs). JavaScript is flexible — you can call a function with fewer or more arguments than defined:
function greet(name, greeting) {
console.log(`${greeting}, ${name}!`);
}
greet("Alice", "Hello"); // "Hello, Alice!"
greet("Bob"); // "undefined, Bob!" — missing args become undefined
// Default parameters (ES6)
function greetDefault(name, greeting = "Hello") {
console.log(`${greeting}, ${name}!`);
}
greetDefault("Alice", "Hi"); // "Hi, Alice!"
greetDefault("Bob"); // "Hello, Bob!" — uses default
// Default values can be expressions
function createUser(name, role = "viewer", joinDate = new Date()) {
return { name, role, joinDate };
}
console.log(createUser("Alice"));
// { name: "Alice", role: "viewer", joinDate: [current date] }
console.log(createUser("Bob", "admin"));
// { name: "Bob", role: "admin", joinDate: [current date] }
Rest Parameters
// Collect remaining arguments into an array
function sum(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
// Rest must be the last parameter
function logAll(first, ...rest) {
console.log(`First: ${first}`);
console.log(`Rest: ${rest}`);
}
logAll("a", "b", "c", "d");
// "First: a"
// "Rest: b,c,d"
Return Values
Functions send data back using return. A function without a
return statement (or with an empty return) returns
undefined.
// Returning a value
function multiply(a, b) {
return a * b;
}
const product = multiply(4, 5);
console.log(product); // 20
// Early return (guard clause pattern)
function divide(a, b) {
if (b === 0) {
return "Cannot divide by zero";
}
return a / b;
}
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // "Cannot divide by zero"
// Returning objects (wrap in parentheses with arrow functions)
const makeUser = (name, age) => ({ name, age });
console.log(makeUser("Alice", 25)); // { name: "Alice", age: 25 }
// Functions without return give undefined
function doSomething() {
console.log("Working...");
// no return statement
}
const result = doSomething(); // prints "Working..."
console.log(result); // undefined
Scope
Scope determines where variables are accessible. JavaScript has three types of scope:
Global Scope
// Variables declared outside any function or block are global
const appName = "MyApp";
let userCount = 0;
function showApp() {
// Global variables are accessible everywhere
console.log(appName); // "MyApp"
userCount++;
}
showApp();
console.log(userCount); // 1
Function (Local) Scope
function calculateTax(amount) {
const taxRate = 0.2; // local to this function
const tax = amount * taxRate;
return tax;
}
console.log(calculateTax(100)); // 20
// console.log(taxRate); // ReferenceError: taxRate is not defined
// console.log(tax); // ReferenceError: tax is not defined
Block Scope
// let and const are block-scoped (limited to { })
if (true) {
const blockConst = "I'm block-scoped";
let blockLet = "Me too";
console.log(blockConst); // works
console.log(blockLet); // works
}
// console.log(blockConst); // ReferenceError
// console.log(blockLet); // ReferenceError
// for loop scope
for (let i = 0; i < 3; i++) {
// i only exists inside this loop
}
// console.log(i); // ReferenceError
Variables declared with var are function-scoped, not
block-scoped. This means a var inside an if,
for, or while block leaks out to the surrounding
function. It also gets "hoisted" to the top of the function (the declaration
moves up, but the assignment stays where it is), which can cause variables
to be undefined unexpectedly.
// var hoisting demonstration
console.log(x); // undefined (not an error — var is hoisted)
var x = 5;
console.log(x); // 5
// What JavaScript actually sees:
// var x; // declaration hoisted to top
// console.log(x); // undefined
// x = 5; // assignment stays here
// console.log(x); // 5
// let/const do NOT allow this
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
// let y = 5;
Closures
A closure is a function that remembers the variables from the scope where it was created, even after that scope has finished executing. This is one of JavaScript's most powerful features.
// Basic closure
function createGreeter(greeting) {
// The inner function "closes over" the greeting variable
return function(name) {
console.log(`${greeting}, ${name}!`);
};
}
const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");
sayHello("Alice"); // "Hello, Alice!"
sayHi("Bob"); // "Hi, Bob!"
// greeting is "remembered" even though createGreeter has returned
Practical Closure: Counter
function createCounter(start = 0) {
let count = start;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
reset: () => { count = start; }
};
}
const counter = createCounter(10);
console.log(counter.getCount()); // 10
counter.increment();
counter.increment();
console.log(counter.getCount()); // 12
counter.decrement();
console.log(counter.getCount()); // 11
counter.reset();
console.log(counter.getCount()); // 10
// count is private — there's no way to access it directly
// console.log(counter.count); // undefined
Closure Gotcha: Loops
// Common mistake with var in loops
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // prints 3, 3, 3 (not 0, 1, 2)
}, 100);
}
// var is function-scoped, so all callbacks share the same i
// Fix: use let (block-scoped — each iteration gets its own i)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // prints 0, 1, 2
}, 100);
}
Callback Functions
A callback is a function passed as an argument to another function. Callbacks are fundamental to JavaScript — they're used for event handling, array methods, timers, and asynchronous operations.
// Passing a function as an argument
function doMath(a, b, operation) {
return operation(a, b);
}
const sum = doMath(5, 3, (a, b) => a + b);
const product = doMath(5, 3, (a, b) => a * b);
console.log(sum); // 8
console.log(product); // 15
Callbacks with Array Methods
const numbers = [1, 2, 3, 4, 5];
// forEach — run a function on each element
numbers.forEach(num => console.log(num * 2));
// 2, 4, 6, 8, 10
// map — transform each element (returns new array)
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter — keep elements that pass a test
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
// find — get the first element that passes a test
const firstBig = numbers.find(num => num > 3);
console.log(firstBig); // 4
Callbacks with Timers
// setTimeout — run once after a delay (milliseconds)
setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);
// setInterval — run repeatedly at an interval
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Tick ${count}`);
if (count >= 5) {
clearInterval(intervalId); // stop after 5 ticks
}
}, 1000);
Callbacks with Event Listeners
// In a browser environment:
// document.getElementById("myButton").addEventListener("click", function(event) {
// console.log("Button clicked!");
// console.log(event.target); // the element that was clicked
// });
// Arrow function version:
// document.getElementById("myButton").addEventListener("click", (event) => {
// console.log("Button clicked!");
// });
Summary
- Function declarations are hoisted — can be called before they appear in code
- Function expressions assign functions to variables — not hoisted
- Arrow functions provide concise syntax and inherit
thisfrom their scope - Default parameters let you set fallback values:
(name = "World") - Rest parameters collect extra arguments:
(...args) - Functions return
undefinedunless you explicitlyreturna value - Scope: global, function (local), and block (
let/constinside{ }) - Closures remember variables from their creation scope — essential for data privacy and factory patterns
- Callbacks are functions passed to other functions — used everywhere in JavaScript
You now understand how to write, organize, and compose functions in JavaScript. Next up: arrays and objects — the two most important data structures you will work with every day.