Functions Fundamentals
Functions Fundamentals
Functions are reusable blocks of code — the single most important building block in JavaScript. Understanding the different ways to define them, how arguments flow in and values flow out, and how this behaves will carry you through every framework and library you ever touch.
Step 1: Function Declarations
function greet(name) {
return Hello, ${name}!;
}
greet("World"); // "Hello, World!"
Why it works: a declaration is hoisted — the whole function is registered before any code runs, so you may call greet() on a line above its definition. The return statement hands a value back to the caller; without it, the function returns undefined.
Step 2: Function Expressions
const greet = function (name) {
return Hello, ${name}!;
};
Here the function is a value assigned to a variable. Expressions are not hoisted the way declarations are: calling greet() before this line throws a ReferenceError. Treating functions as values is what lets JavaScript pass them around like any other data — the foundation of callbacks.
Step 3: Arrow Functions (ES6)
// Full form
const add = (a, b) => {
return a + b;
};
// Implicit return — no braces means "return this expression"
const addShort = (a, b) => a + b;
// One parameter needs no parentheses
const double = n => n * 2;
Why arrows are more than short syntax: arrow functions do not create their own this, arguments, or super — they inherit this from the surrounding code. That makes them perfect for callbacks inside methods, where a regular function would suddenly have a different this:
const counter = {
count: 0,
startRegular() {
setInterval(function () {
this.count++; // BROKEN: "this" is not counter here
}, 1000);
},
startArrow() {
setInterval(() => {
this.count++; // works: arrow inherits counter's "this"
}, 1000);
}
};
The flip side: arrows are the wrong choice for object methods themselves and for anything needing its own this.
Step 4: Default and Rest Parameters
function greet(name = "World") {
return Hello, ${name}!;
}
greet(); // "Hello, World!"
greet("Alice"); // "Hello, Alice!"
// Rest gathers any number of arguments into a real array
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
Why it works: a default kicks in only when the argument is undefined (missing, or explicitly undefined) — passing null or 0 does not trigger it, which surprises many beginners. The rest parameter ...numbers must come last and gives you a genuine array, unlike the old arguments object.
Step 5: Higher-Order Functions
A higher-order function takes a function as input or returns one — and this pattern is everywhere: map, filter, event listeners, middleware.
// Takes a function
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log); // 0, 1, 2
// Returns a function
function multiplier(factor) {
return n => n * factor;
}
const triple = multiplier(3);
triple(5); // 15
Why triple remembers factor: the returned arrow function forms a closure — it keeps access to the variables of the scope where it was created, even after multiplier has finished running. Closures are how JavaScript functions carry private state, and they power everything from debouncing to React hooks.
Common Beginner Errors and Fixes
- Calling instead of passing:
button.addEventListener("click", handle())runshandleimmediately and passes its return value. Fix: pass the reference —handle— or wrap it:() => handle(id). - Missing return:
const f = (n) => { n * 2 }returnsundefinedbecause braces need an explicitreturn. Fix:(n) => n * 2or addreturn. - Returning an object literal from an arrow:
() => { name: "x" }is parsed as a code block. Fix: wrap in parentheses —() => ({ name: "x" }). - Expecting a default for null:
greet(null)prints "Hello, null!". Fix: normalize inputs, or usename ?? "World"inside the function. - Losing
thisin a callback: extracting a method (const fn = obj.method) detaches it fromobj. Fix:obj.method.bind(obj)or an arrow wrapper.
Practice Exercise
Build a tiny formatting toolkit:
capitalize(word) that upper-cases the first letter.exclaim with an implicit return that appends "!".compose(f, g) that returns a new function applying g first, then f.const shout = compose(exclaim, capitalize) and test shout("hello") — it should print "Hello!".compose with rest parameters so it accepts any number of functions.function capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
const exclaim = s => s + "!";
function compose(f, g) {
return x => f(g(x));
}
const shout = compose(exclaim, capitalize);
console.log(shout("hello")); // "Hello!"
If the bonus stumps you: reduce over the functions array, threading the value through each call.