Functions

A reusable, named block of code that performs a specific task.

What is it?

As programs grow, you end up needing to do the same task in multiple places — calculate a total, validate an email, format a date. Instead of repeating that logic everywhere, you wrap it in a function: a named block of code you can "call" whenever you need it.

A function can accept inputs (called parameters) and can send back a result (using return).

Explain like I'm 10

A function is like a recipe. You give it ingredients (parameters), it follows a set of steps, and it hands you back a finished dish (the return value). You can reuse the same recipe as many times as you want.

Examples

Declaring and calling a function

function add(a, b) {
  return a + b;
}

const result = add(2, 3);
console.log(result); // 5

add takes two parameters, a and b, and returns their sum. Calling add(2, 3) runs that code with a = 2 and b = 3.

Arrow function shorthand

const add = (a, b) => a + b;

Arrow functions are a shorter way to write simple functions, especially common in modern JavaScript.

How it works

When you call a function, JavaScript pauses the current line, jumps into the function's code, runs it with the arguments you passed in, and — if there's a return statement — sends a value back to exactly where the function was called from. Execution then continues from there.

Why does it exist?

Functions let you name a piece of logic once and reuse it anywhere, which keeps code shorter, easier to test, and easier to change — you only fix a bug in one place instead of everywhere it was copy-pasted.

When to use it

Write a function any time you find yourself about to write the same few lines of logic more than once, or whenever a chunk of code does one clear, nameable thing — validating an email, calculating a total, formatting a date.

When not to use it

Don't wrap a single line you only use once in its own function just for the sake of it — that can add an extra layer of indirection without any real benefit. And if a function is trying to do five unrelated things, that's usually a sign it should be split into several smaller, focused functions instead.

Common mistakes

  • Forgetting to return a value and expecting the function to give one back automatically.

  • Confusing a function's parameters (the placeholders) with arguments (the actual values passed in).

  • Writing functions that try to do too many unrelated things at once.

Practice exercises

  1. Easy:

    Write a function square(n) that returns n * n.

  2. Medium:

    Write a function isEven(n) that returns true or false.

  3. Hard:

    Write a function average(numbers) that takes an array and returns the average value.

Interview questions

What's the difference between a parameter and an argument?

A parameter is the placeholder name written in the function's own definition (function add(a, b) — a and b are parameters). An argument is the actual value supplied at the call site (add(2, 3) — 2 and 3 are arguments).

What happens if a function has no `return` statement?

It implicitly returns undefined once execution reaches the end of the function body — calling it never throws, but any code that tries to use its "result" will be working with undefined.

What's the difference between a function declaration and an arrow function?

Beyond the shorter syntax, arrow functions don't have their own this, arguments object, or super, and can't be used as constructors with new — they inherit this from the surrounding scope instead of getting a new one based on how they're called. Function declarations get their own this (determined by the call) and their own arguments object.

What's the difference between a function declaration and a function expression?

A function declaration (function foo() {}) is a standalone statement that's hoisted along with its full body, so it can be called earlier in the code than where it's written. A function expression (const foo = function () {}) creates the function as part of an assignment; the variable follows normal let/const/var hoisting rules, so the function itself isn't callable until that assignment line has actually run.

Can a function have more than one `return` statement?

Yes — a function can contain as many return statements as needed, often one per branch of an if/else. Whichever one actually executes first ends the function immediately at that point; any code after it in the function body doesn't run.

What happens if you call a function with fewer arguments than it has parameters?

The missing parameters are simply set to undefined (or to their default value, if one is specified) — JavaScript doesn't throw an error or complain about a mismatched argument count, unlike many statically typed languages.

What happens if you call a function with more arguments than it has declared parameters?

The extra arguments are accepted without error; they're just not bound to any named parameter. They're still accessible inside the function through the arguments object (in a regular function) or by declaring a rest parameter (...args) to collect them explicitly.

What is a default parameter, and when does its default value actually kick in?

A value specified in the function signature (function greet(name = "Guest")) that's used only when the corresponding argument is undefined — either because it was omitted entirely, or explicitly passed as undefined. Passing any other falsy value, like null or 0, does not trigger the default.

What is a rest parameter, and how does it differ from the old `arguments` object?

...args as the last parameter collects any remaining arguments into a real array (function sum(...nums) { ... }), so array methods like .map() and .reduce() work on it directly. The older arguments object is available in every regular function automatically without declaring anything, but it's only array-like — it lacks array methods, and it isn't available at all inside arrow functions.

Are function declarations hoisted? What about function expressions?

Function declarations are hoisted completely, body included, so they can be called before the line they're written on. Function expressions (and arrow functions) are not hoisted as callable functions — only the variable they're assigned to follows its own declaration's hoisting rules (undefined for var, or the TDZ for let/const), so calling one before its assignment line either gets undefined is not a function or a TDZ ReferenceError.

What is a callback function?

A function passed as an argument into another function, to be invoked later — usually once some operation finishes or some event occurs, like the click handler passed to addEventListener, or the function passed to .map() that's run once per array element.

What is a higher-order function?

A function that either takes another function as an argument, returns a function as its result, or both — array methods like .map(), .filter(), and .reduce() are all higher-order functions because each one accepts a callback.

What is a closure, in plain terms?

A function that remembers the variables from the scope it was defined in, even after that outer scope has finished running — so a function returned from another function can keep reading and updating variables that technically belong to a call that's already completed.

What is recursion, and what must a recursive function have to avoid infinite recursion?

A function that calls itself to solve a smaller version of the same problem, repeating until it reaches a base case simple enough to answer directly without recursing further. Without that base case — a condition that stops the recursive calls — the function keeps calling itself indefinitely until it exceeds the call stack size and throws a RangeError: Maximum call stack size exceeded.

What is an IIFE (Immediately Invoked Function Expression), and why would you use one?

A function expression that's defined and called in the same statement, typically written as (function () { ... })();. It creates a private scope that runs once immediately — historically used before let/const/modules existed to avoid leaking variables into the global scope; it's much less necessary today but still shows up for one-off setup code.

Can a function be assigned to a variable, passed as an argument, or returned from another function? What does that make functions in JavaScript?

Yes to all three — this makes functions "first-class citizens" (or first-class values): they can be treated exactly like any other value (a number, a string, an object), stored, passed around, and returned, which is what makes patterns like callbacks, higher-order functions, and closures possible in the first place.

What's the practical difference between `function foo() {}` and `const foo = function () {}`?

Both create an equivalent function, but the first is a hoisted declaration usable before its line runs, while the second is an expression bound to a const, unusable until that assignment line executes. A subtler difference: the declaration's function always has the name foo for stack traces even if reassigned elsewhere, while a truly anonymous expression only gets an inferred name from the variable it's assigned to at creation time.

Why don't arrow functions have their own `this`? Where do they get it from instead?

Arrow functions were deliberately designed without their own this binding — they don't create a new this when called, but instead look up this lexically, exactly like any other variable: from the nearest enclosing non-arrow function (or the module/global scope) at the point where the arrow function was defined, not where it's called from.

Why can't arrow functions be used as constructors with `new`?

Using new relies on the function creating its own fresh this bound to the newly constructed object — arrow functions never create their own this at all, so there's nothing for new to bind, and JavaScript throws a TypeError: X is not a constructor if you try.

What is a pure function? Why are pure functions easier to test and reason about?

A function that, given the same inputs, always returns the same output and causes no observable side effects — it doesn't modify anything outside itself (no mutating arguments, no touching global state, no I/O). That makes it trivial to test in isolation (call it, check the output, no setup or teardown needed) and safe to reason about without tracking any state beyond its own inputs.

What does it mean for JavaScript functions to be 'first-class citizens'?

It means functions are treated as regular values rather than a special separate category — they can be stored in variables, put into arrays or object properties, passed as arguments, and returned from other functions, just like any number or string could be.

What's the difference between a function's arity and the number of arguments it's actually called with?

Arity is a fixed property of the function's own definition — the number of parameters it declares (function add(a, b) {} has an arity of 2). The number of arguments is decided per call site and can be more, fewer, or exactly equal to the arity; JavaScript doesn't enforce that the two match.

Are extra arguments passed beyond a function's declared parameters lost?

No — they're simply not assigned to a named parameter, but they still exist and are reachable, either through the arguments object in a regular function, or by explicitly capturing them with a rest parameter (...rest).

What is currying, and how does it relate to closures?

Transforming a function that takes multiple arguments into a sequence of functions that each take one argument and return the next function in the chain, e.g. add(a)(b)(c) instead of add(a, b, c). It relies entirely on closures — each returned inner function keeps access to the arguments already supplied to the outer functions that produced it.

Why can't arrow functions have their own `arguments` object?

It follows the same design as this — arrow functions don't create their own execution context binding for arguments either, so referencing arguments inside one looks it up lexically from the nearest enclosing regular function instead. Referencing arguments inside an arrow function with no enclosing regular function throws a ReferenceError.

Output-prediction: what does `function outer() { let count = 0; return function () { count++; return count; }; } const counter = outer(); console.log(counter()); console.log(counter());` log?

It logs 1 then 2. outer() runs once and returns the inner function, but that inner function keeps a closure over count from that specific call — each time counter() is invoked afterward, it reads and updates that same remembered count rather than starting fresh.

Output-prediction: what does `sayHi(); function sayHi() { console.log("hi"); }` do?

It logs "hi" successfully. Function declarations are hoisted along with their entire body, so sayHi is already fully defined and callable before execution even reaches the line where it's written.

Output-prediction: what does `sayBye(); const sayBye = function () { console.log("bye"); };` do?

It throws ReferenceError: Cannot access 'sayBye' before initialization. sayBye is a const binding, so it's hoisted but left in the Temporal Dead Zone until its declaration line runs — the fact that the value being assigned is a function doesn't change how const hoisting works.

Output-prediction: given `const obj = { name: "Ana", greet() { console.log(this.name); }, greetArrow: () => console.log(this.name) };`, what does `obj.greet()` log compared to `obj.greetArrow()`?

obj.greet() logs "Ana" — a regular method's this is determined by its call-site, and calling it as obj.greet() binds this to obj. obj.greetArrow() does not log "Ana" — since arrow functions inherit this lexically from their surrounding scope rather than from how they're called, this here is whatever this was outside the object literal, never obj, regardless of how greetArrow is invoked.

Output-prediction: given `function add(a, b = 10) { return a + b; }`, what do `add(5)`, `add(5, undefined)`, and `add(5, null)` each return?

15, 15, then 5. The default parameter only applies when the argument is exactly undefined — both omitting it and passing undefined explicitly trigger the default 10. Passing null, however, is a real, present value, so the default is skipped and b stays null; 5 + null then coerces null to 0 in numeric addition, giving 5.