JavaScript Functions
Functions, Closures & this
Functions are values in JavaScript — they can be stored, passed around, and returned from other functions. That single fact powers half the language's idioms.
ES2023
Interview Key
4 Topics
01
Function Forms
Three ways to write a function, each with different rules for hoisting and this.
JavaScript — Declaration, Expression, Arrow
// Function declaration — hoisted, has its own `this` function add(a, b) { return a + b; } // Function expression — not hoisted, has its own `this` const subtract = function(a, b) { return a - b; }; // Arrow function — not hoisted, NO own `this` (inherits from enclosing scope) const multiply = (a, b) => a * b; // Default & rest parameters function greet(name = "stranger", ...titles) { return `Hello, ${titles.join(" ")} ${name}`; } greet(); // "Hello, stranger" greet("Ada", "Dr.", "Countess"); // "Hello, Dr. Countess Ada"
Default rule: use arrow functions for callbacks and anything that should inherit the outer
this; use regular functions (or methods) when you need the function's own this or a name that shows up in stack traces.02
Hoisting
JavaScript moves declarations to the top of their scope before running code — but what gets hoisted differs by form.
JavaScript — What Actually Hoists
sayHi(); // "Hi!" — works, fully hoisted function sayHi() { console.log("Hi!"); } // console.log(x); // undefined — var hoists, but not its value var x = 5; // console.log(y); // ReferenceError — let/const hoist into a "dead zone" let y = 10; // sayBye(); // TypeError: sayBye is not a function const sayBye = () => console.log("Bye");
Only function declarations are fully hoisted (usable before their line).
var hoists the name only (value is undefined until reached). let/const hoist into a temporal dead zone that throws if touched early.03
Closures
A closure is a function that remembers the variables from where it was created, even after that outer function has returned. This is how private state and factory functions work in JS without classes.
JavaScript — Counter Factory
function makeCounter() { let count = 0; // private — no outside access return function() { return ++count; }; } const counterA = makeCounter(); const counterB = makeCounter(); counterA(); // 1 counterA(); // 2 — counterA has its own `count` counterB(); // 1 — independent closure, separate `count` // Classic pitfall: closures in a loop const fns = []; for (let i = 0; i < 3; i++) fns.push(() => i); fns.map(f => f()); // [0, 1, 2] — `let` gives each iteration its own i
04
this & Binding
this is decided by how a function is called, not where it's defined — the single biggest source of confusion for people coming from Python's explicit self.
JavaScript — this in Four Contexts
const user = { name: "Ada", greet() { return `Hi, ${this.name}`; } // method — `this` = user }; user.greet(); // "Hi, Ada" const detached = user.greet; // detached(); // "Hi, undefined" — lost its receiver const bound = user.greet.bind(user); bound(); // "Hi, Ada" — permanently locked to user // Arrow functions capture `this` from where they're WRITTEN, not called const arrowUser = { name: "Grace", greet: () => `Hi, ${this.name}` // `this` here is NOT arrowUser };
| Call style | this refers to |
|---|---|
obj.method() | The object before the dot |
fn() (plain call) | undefined in strict mode |
fn.call(obj) / fn.apply(obj) | Explicitly whatever you pass |
| Arrow function | Whatever this was in the surrounding scope |
✓
Quick Quiz
1. Which form is fully hoisted and callable before its line?
2. A closure lets an inner function…
3. Arrow functions get their `this` from…
4. fn.bind(obj) returns…
5. function greet(name = "x", ...titles) — titles is…