Functions, Closures and this

Function declarations vs expressions vs arrow functions, closures, this binding, and prototypes.

Three ways to write a function

Javascript
// Function declaration — hoisted, can be called before its definition appears
function add(a, b) {
  return a + b;
}

// Function expression — not hoisted, assigned to a variable like any value
const subtract = function (a, b) {
  return a - b;
};

// Arrow function — concise syntax, and (crucially) no own `this`
const multiply = (a, b) => a * b;

Function declarations are hoisted with their full body, so calling add() before its line in the file still works. Function expressions and arrow functions are not — the variable exists (if let/const) but isn't callable until execution reaches that line.

Arrow functions have a few syntax shortcuts worth knowing:

Javascript
const square = n => n * n;              // single param: parens optional
const greet = () => "Hello!";           // no params: parens required
const makePoint = (x, y) => ({ x, y }); // returning an object literal needs parens around it

Closures

A closure is a function that "remembers" the variables from the scope it was created in, even after that outer scope has finished executing. This is one of JavaScript's most powerful — and most commonly interview-tested — features.

Javascript
function makeCounter() {
  let count = 0;               // private state, not accessible from outside

  return function () {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
console.log(counter());   // 1
console.log(counter());   // 2
console.log(counter());   // 3 — `count` persisted between calls

makeCounter() returns, but the inner function still has access to count — it "closed over" that variable. Each call to makeCounter() creates a brand-new, independent count:

Javascript
const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA());  // 1
console.log(counterA());  // 2
console.log(counterB());  // 1 — a separate closure, separate state

Closures are the mechanism behind private state in JavaScript (before class private fields existed), memoization caches, and event handler factories.

How this works — and why arrow functions are different

In a regular function, this is determined by how the function is called, not where it's defined:

Javascript
const user = {
  name: "Ada",
  greet: function () {
    console.log(`Hi, I'm ${this.name}`);
  },
};

user.greet();   // "Hi, I'm Ada" — this === user, because it was called as user.greet()

const detached = user.greet;
detached();     // "Hi, I'm undefined" (or a TypeError in strict mode) — this is no longer user!

This is a classic source of bugs — passing a method as a callback loses its this:

Javascript
class Timer {
  constructor() {
    this.seconds = 0;
  }

  tick() {
    this.seconds += 1;   // relies on `this` being the Timer instance
    console.log(this.seconds);
  }
}

const timer = new Timer();
// setInterval(timer.tick, 1000);  // BUG: `this` inside tick() is undefined here

Arrow functions solve this by not having their own this at all — they inherit this lexically from the scope where they were defined, not from how they're called:

Javascript
class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    setInterval(() => {
      this.seconds += 1;   // `this` is inherited from start()'s `this` — the Timer instance
      console.log(this.seconds);
    }, 1000);
  }
}

The other traditional fixes are .bind(this), or capturing const self = this; before the callback — both now largely superseded by just using an arrow function.

call, apply, and bind

These let you explicitly control what this refers to inside a regular function:

Javascript
function introduce() {
  return `Hi, I'm ${this.name}`;
}

const ada = { name: "Ada" };

console.log(introduce.call(ada));           // "Hi, I'm Ada" — calls immediately with this = ada
console.log(introduce.apply(ada));          // same as .call, but takes args as an array

const boundIntroduce = introduce.bind(ada); // returns a new function permanently bound to ada
console.log(boundIntroduce());              // "Hi, I'm Ada"

Prototypes, briefly

Every JavaScript object has an internal link to another object — its prototype — which it falls back to when a property lookup fails on the object itself. This prototype chain is how inheritance worked before (and still works underneath) the class syntax:

Javascript
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound`;
};

const dog = new Animal("Rex");
console.log(dog.speak());   // "Rex makes a sound" — found via the prototype chain

Modern class syntax is largely syntactic sugar over this same prototype mechanism:

Javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Animal above still creates a constructor function under the hood, with speak attached to Animal.prototype — the class syntax is just far more readable than manually assigning to .prototype.

Common mistakes

  • Passing an object method as a bare callback (element.addEventListener("click", obj.method)) and losing this — use an arrow function wrapper or .bind().
  • Using a regular function for a callback that needs the enclosing this (e.g., inside a class method) instead of an arrow function.
  • Assuming closures capture a variable's value at creation time — they actually capture the variable itself, which matters a lot inside loops (see the classic var loop closure bug, largely fixed by using let instead).

Interview questions

Q: What is a closure? Give a practical use case. A closure is a function bundled together with references to the variables from its enclosing scope, which it retains access to even after that outer scope has returned. A practical use case is a counter or cache factory — makeCounter() returns a function that keeps its own private, persistent count variable no outside code can directly touch.

Q: Why do arrow functions behave differently with this? A regular function's this is determined dynamically by its call site (obj.method() vs. a detached call). An arrow function has no this of its own at all — it lexically inherits this from the scope it was textually defined in, which is why arrow functions are the standard fix for losing this inside a callback passed out of a class method.