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 stylethis 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 functionWhatever 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…