Nailing the Hardest JavaScript Interview Questions

1 min read

Your Guide to the 'Tricky' JavaScript Interview Questions

Every JavaScript interview has a section designed to test your deep understanding of the language. They don't just want to know if you can use a framework, they want to know if you understand the language it's built on.

These 'tricky' questions almost always fall into one of four categories:

  1. this Keyword: What does this refer to? How can you change it?
  2. Closures: What is a closure? Why is it useful?
  3. Scope & Hoisting: What's the difference between var, let, and const?
  4. Prototypes: How does inheritance really work in JavaScript?

If you can confidently explain these four concepts, you will prove you are a serious developer. In this cluster, we'll break down each one with simple, clear examples that you can use in your interview.

Understanding `this`: A Guide to `call()`, `apply()`, and `bind()`

Interview Question: 'What is the this keyword?'

Answer: 'In JavaScript, this is a special keyword that refers to the execution context of a function—basically, how and where a function is called determines what this will be.'

Its value is dynamic and depends on the call site:

  • Global: In the global scope, this refers to the window object (in a browser).
  • Object Method: When a function is called as a method of an object, this is the object itself (e.g., person.sayHi() makes this the person object).
  • Event Listener: In a DOM event listener, this is typically the element that the listener is attached to.
  • Arrow Functions: This is the exception! Arrow functions (=>) do not have their own this. They lexically inherit this from their parent scope.

How to Control this: call, apply, bind

An interviewer may ask, 'How can you explicitly set what this is?'

1. .bind() - Returns a new function

.bind() creates a new function that, when called, has its this keyword permanently set to the provided value.

const person = {
  name: 'Alice',
  sayHi: function() {
    console.log(`Hi, I'm ${this.name}`);
  }
};

const person2 = { name: 'Bob' };

// 'Bind' person.sayHi to 'person2'
// This creates a new function.
const sayHiAsBob = person.sayHi.bind(person2);

sayHiAsBob(); // Output: 'Hi, I'm Bob'

// The original function is unchanged
person.sayHi(); // Output: 'Hi, I'm Alice'

2. .call() - Calls the function immediately

.call() invokes the function immediately, setting this to the first argument. Any subsequent arguments are passed as individual arguments to the function.

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const alice = { name: 'Alice' };
const bob = { name: 'Bob' };

// Call 'introduce' with 'this' as 'alice'
introduce.call(alice, 'Hello', '!'); // Output: 'Hello, I'm Alice!'

// Call 'introduce' with 'this' as 'bob'
introduce.call(bob, 'Hi', '.'); // Output: 'Hi, I'm Bob.'

3. .apply() - Calls the function immediately

.apply() is identical to .call(), but it takes its arguments as an array. This is the only difference.

// Same function as before
function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const alice = { name: 'Alice' };
const args = ['Good morning', '...'];

// Call 'introduce' with 'this' as 'alice' and 'args' as an array
introduce.apply(alice, args); // Output: 'Good morning, I'm Alice...'

// Mnemonic: 'A' for 'Apply' and 'Array'. 'C' for 'Call' and 'Comma' (separated args).

Closures Explained in 5 Minutes

Interview Question: 'What is a closure?'

This is a classic. A simple, confident answer is best.

Answer: 'A closure is a function that has access to its outer function's scope (its variables), even after the outer function has returned. In other words, the inner function 'remembers' its parent's variables.'

It's like the inner function carries a 'backpack' (its 'lexical environment') with it, and that backpack contains the variables from its parent's scope.


The Classic Code Example:

This is the canonical example of a closure. An interviewer may write this and ask you what it does.

// 1. 'outerFunction' is our parent function.
function outerFunction(outerVariable) {

  // 2. 'innerFunction' is the closure. It has access
  //    to 'outerVariable' from its parent's scope.
  function innerFunction(innerVariable) {
    console.log('Outer:', outerVariable, 'Inner:', innerVariable);
  }

  // 3. 'outerFunction' returns 'innerFunction'
  return innerFunction;
}

// 4. We call 'outerFunction'. It runs and returns.
//    At this point, 'outerFunction' is gone.
//    We assign the returned function to 'newFunction'.
const newFunction = outerFunction('I am the outer var');

// 5. Now we call 'newFunction'. Even though 'outerFunction'
//    is long gone, 'newFunction' remembers 'outerVariable'
//    from its 'backpack'.
newFunction('I am the inner var');

// --- CONSOLE OUTPUT --- 
// Outer: I am the outer var Inner: I am the inner var

A Practical Use Case: Data Privacy

Closures are perfect for creating 'private' variables. This is a common pattern before class syntax was available.

function createCounter() {
  // 'count' is a 'private' variable, protected by the closure.
  // Nothing outside 'createCounter' can touch it.
  let count = 0;

  // We return an object with methods that can access 'count'
  return {
    increment: function() {
      count++;
      console.log('Count is now', count);
    },
    decrement: function() {
      count--;
      console.log('Count is now', count);
    }
  };
}

const myCounter = createCounter();
myCounter.increment(); // Output: Count is now 1
myCounter.increment(); // Output: Count is now 2

// You cannot do this! 'count' is not accessible.
// console.log(myCounter.count); // Output: undefined

Prototypal Inheritance vs. the `class` Keyword

Interview Question: 'How does inheritance work in JavaScript?'

This is a trick question. The interviewer wants to see if you know about prototypal inheritance.

Answer: 'While JavaScript has a class keyword, that's just syntactic sugar. True JavaScript inheritance is prototypal. Every object has a hidden [[Prototype]] property (often exposed as __proto__) that links to another object. When you try to access a property on an object, if it's not found, JavaScript walks up the prototype chain to look for it on the parent, and the parent's parent, and so on.'


'Before': Prototypal Inheritance (The 'Old' Way)

This is how inheritance worked before ES6. It's clunky but shows the mechanism.

// 1. This is our 'parent class' (a constructor function)
function Animal(name) {
  this.name = name;
}

// 2. We add a method to its 'prototype' object
Animal.prototype.speak = function() {
  console.log(this.name + ' makes a noise.');
}

// 3. This is our 'child class'
function Dog(name) {
  // Call the parent constructor to set 'this.name'
  Animal.call(this, name);
}

// 4. This is the magic: link the prototypes!
// Set Dog's prototype to be a new object created from Animal's prototype
Dog.prototype = Object.create(Animal.prototype);

// 5. Override the parent method
Dog.prototype.speak = function() {
  console.log(this.name + ' barks.');
}

const fido = new Dog('Fido');
fido.speak(); // Output: 'Fido barks.'

// 'fido' -> Dog.prototype -> Animal.prototype -> Object.prototype -> null

'After': The class Keyword (ES6 Sugar)

The class keyword does exactly the same thing as the code above, but it's much cleaner and easier to read. It's still prototypes under the hood.

// 1. Parent class
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

// 2. Child class uses 'extends' and 'super()'
class Dog extends Animal {
  constructor(name) {
    super(name); // Calls the parent constructor
  }

  speak() {
    console.log(this.name + ' barks.'); // Overrides parent
  }
}

const fido = new Dog('Fido');
fido.speak(); // Output: 'Fido barks.'

The simple answer: 'JavaScript uses prototypal inheritance. The class keyword is just syntactic sugar that makes it easier to create constructor functions and link their prototypes using extends.'

`var` vs. `let` vs. `const`: Understanding Scope and Hoisting

Interview Question: 'What's the difference between var, let, and const?'

This is one of the most common JS fundamentals questions. Your answer must include the words 'scope' and 'hoisting'.


1. var (The 'Old' Way)

  • Scope: Function-scoped. A var variable is only 'private' to the function it's declared in. It does not respect block scope (like if statements or for loops).
  • Hoisting: Hoisted and initialized. var variables are 'lifted' to the top of their function scope and initialized with undefined. This means you can access them before they are declared, and you'll get undefined.

Code Example for var:

function testVar() {
  console.log(myVar); // Output: undefined (due to hoisting)

  if (true) {
    var myVar = 'Hello';
    console.log(myVar); // Output: Hello
  }

  // 'myVar' leaked out of the 'if' block!
  console.log(myVar); // Output: Hello
}
testVar();

2. let (The 'New' Way)

  • Scope: Block-scoped. A let variable is 'private' to its nearest 'block' (e.g., an if, for, or just {...}). This is much more predictable.
  • Hoisting: Hoisted but NOT initialized. let variables are 'lifted', but they are placed in a 'Temporal Dead Zone' (TDZ). If you try to access one before its declaration, you get a ReferenceError, not undefined.

Code Example for let:

function testLet() {
  // console.log(myLet); // Output: ReferenceError! (TDZ)

  if (true) {
    let myLet = 'Hello';
    console.log(myLet); // Output: Hello
  }

  // 'myLet' did NOT leak. It is block-scoped.
  // console.log(myLet); // Output: ReferenceError!
}
testLet();

3. const (The 'Immutable' Way)

  • Scope: Block-scoped (just like let).
  • Hoisting: Hoisted but NOT initialized (just like let, it's in the TDZ).
  • Key Difference: You must initialize it at declaration, and you cannot reassign it.

Important Gotcha: const does not make objects or arrays immutable. It just protects the variable assignment. You can still change the contents of an object.

Code Example for const:

const myName = 'Alice';
// myName = 'Bob'; // Output: TypeError: Assignment to constant variable.

const myObject = { id: 1 };

// This is perfectly fine!
myObject.id = 2;
myObject.name = 'Bob';
console.log(myObject); // Output: { id: 2, name: 'Bob' }

// This is NOT allowed (reassignment)
// myObject = { id: 3 }; // Output: TypeError!

đź’¬