· 8 min read

The 7 Most Tricky JavaScript Questions Interviewers Love to Ask

Walk through seven of the trickiest JavaScript interview questions: what interviewers are really testing, step-by-step explanations, concise sample answers, code examples, and smart strategies to answer confidently.

Introduction

Interviewers often ask deceptively simple-sounding JavaScript questions that separate people who can reason about the language from those who just memorize facts. In this article we’ll walk through the 7 most common “tricky” JavaScript questions you may be asked, explain what the interviewer is testing, provide clear, practical answers with code examples, and give concise strategies to answer under pressure.

Quick advice before we begin

  • Start your answer with a short, high-level summary (one or two sentences).
  • Then demonstrate with a minimal code example.
  • Always mention edge cases or gotchas the interviewer may probe.
  • Explain why the behavior exists (spec or engine detail), not just what it is.
  1. “Explain how ‘this’ is determined in JavaScript.”

Why they’re asking

This checks whether you understand invocation context (call-site), and whether you know how arrow functions differ. Many bugs and architectural mistakes come from misunderstanding ‘this’.

Short answer you can say in an interview

“‘this’ is set by how a function is called: default/global, method, constructor (new), or explicit via call/apply/bind. Arrow functions don’t get their own ‘this’ - they capture the surrounding lexical this.”

Detailed breakdown with examples

  • Function call (non-strict): ‘this’ defaults to the global object; in strict mode it’s undefined.
  • Method call: the object left of the dot becomes ‘this’.
  • Constructor (new): ‘this’ is a new object linked to the function’s prototype.
  • call/apply/bind: explicitly set ‘this’.
  • Arrow functions: no own ‘this’ - lexical binding.

Example:

function f() {
  console.log(this);
}
const obj = { f };

f(); // non-strict: global / undefined in strict
obj.f(); // 'this' === obj
const bound = f.bind(obj);
bound(); // 'this' === obj

const arrow = () => console.log(this);
arrow(); // lexical 'this' (depends on surrounding scope)

Pitfalls to mention

  • Passing methods as callbacks loses the object binding (use bind or arrow wrapper).
  • Arrow functions are great for callbacks where you want lexical ‘this’, but can’t be used as constructors.

References: MDN: this

  1. “What is a closure? Why do closures cause unexpected behavior in loops?”

Why they’re asking

Closures are fundamental to JS. Interviewers want to see you reason about scope, lifetime, and common pitfalls like capturing loop variables.

Short answer

“A closure is a function that retains access to the lexical environment in which it was created. That means inner functions can access outer variables even after the outer function returns.”

Example of the classic trap

// Tricky behavior with var
const funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function () {
    console.log(i);
  });
}
funcs.forEach(fn => fn()); // prints: 3 3 3

Why that happens

All pushed functions share the same ‘i’ variable (var is function-scoped), so by the time they’re called i is 3.

Correct solutions

  • Use let (block-scoped):
const funcs = [];
for (let i = 0; i < 3; i++) {
  funcs.push(() => console.log(i));
}
funcs.forEach(fn => fn()); // 0 1 2
  • Or capture current value with an IIFE:
for (var i = 0; i < 3; i++) {
  (function (j) {
    funcs.push(() => console.log(j));
  })(i);
}

Pitfalls and interview talking points

  • Mention memory: closures keep referenced variables alive - useful, but can cause leaks if you retain large objects in long-lived closures.
  • Explain why using block scope (let/const) or explicit capture is preferred.

References: MDN: Closures

  1. “Explain hoisting and the Temporal Dead Zone (TDZ).”

Why they’re asking

This probes your knowledge of variable lifetimes and ES6 let/const behavior. It’s a common source of runtime errors.

High-level answer

“Declaration bindings are conceptually moved to the top of their scope - that’s hoisting. But let and const are hoisted differently: they’re in a Temporal Dead Zone from the start of the scope until their declaration is evaluated, and accessing them in the TDZ throws a ReferenceError.”

Examples

console.log(a); // undefined - var is hoisted
var a = 2;

console.log(b); // ReferenceError - b is in TDZ
let b = 3;

Interview strategy

  • Explain var is function-scoped and initialized to undefined during hoisting.
  • Explain let/const are hoisted but uninitialized until the actual line executes (TDZ).
  • Mention const must be initialized at declaration.

References: MDN: var, MDN: let

  1. “Describe the event loop: microtasks vs macrotasks; what’s the execution order for setTimeout, Promise.then and async/await?”

Why they’re asking

This reveals whether you understand concurrency in JS: task queues, microtasks, and how promises interact with the event loop. This matters for correctness in async code.

Short answer

“JavaScript has a single-threaded event loop that processes macrotasks (or tasks) and microtasks. After a macrotask finishes, the engine drains the microtask queue before processing the next macrotask. Promise callbacks (.then/.catch/.finally), MutationObserver callbacks, and async/await continuations use microtasks; setTimeout/setInterval, I/O, and UI rendering callbacks are macrotasks.”

Example (common interview trick):

console.log('script start');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise'));

console.log('script end');

// Output order:
// script start
// script end
// promise
// timeout

async/await behavior

An await pauses the async function and schedules the remainder as a microtask, so it behaves like a Promise.then under the hood:

async function f() {
  console.log('async start');
  await null;
  console.log('async end');
}

f();
console.log('sync');
// Output: async start, sync, async end

Interview strategy

  • Demonstrate the example and explain the microtask queue draining before next macrotask.
  • If asked about browsers vs Node, mention Node has additional phases (timers, I/O, check) but still respects microtask draining semantics (promises run before I/O callbacks in certain phases).

References: MDN: Event Loop

  1. “How does prototypal inheritance work? What’s the difference between prototype and proto?”

Why they’re asking

They want to see whether you understand the prototype chain and object shape, and how class syntax maps to prototypes. Many developers confuse prototypes with instances.

High-level answer

“Objects have an internal prototype link (sometimes exposed as proto). Constructor functions have a .prototype property that becomes the prototype for objects created with new. Property lookup walks this prototype chain. ES6 class syntax is mostly syntactic sugar over prototypes.”

Example:

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  return 'Hi, ' + this.name;
};

const p = new Person('Ava');
console.log(p.greet()); // Hi, Ava
// p.__proto__ === Person.prototype

prototype vs proto

  • function Foo() { }
  • Foo.prototype is the object used when creating new instances with new Foo().
  • obj.proto (or Object.getPrototypeOf(obj)) is the internal prototype link of that particular object.

Interview strategy

  • If asked to implement inheritance, show Object.create or class extends. For example:
const proto = {
  speak() {
    return 'hi';
  },
};
const obj = Object.create(proto);

References: MDN: Prototypes and Inheritance

  1. “What’s the difference between == and ===? Show a surprising coercion example and explain why.”

Why they’re asking

This tests whether you know JavaScript’s abstract equality coercion rules - a favorite source of subtle bugs.

Short answer

”=== checks strict equality without type conversion; == performs type coercion following ECMAScript’s Abstract Equality Comparison rules, which can produce surprising results. Prefer === in normal code and only use == when you intentionally want coercion and know the rules.”

Surprising examples

'' == 0        // true ('' -> 0)
0 == '0'       // true ('0' -> 0)
null == undefined // true (special case)

[] == ![]      // true - tricky!
// Explanation: ![] -> false, so [] == false
// [] -> '' (string) on ToPrimitive -> '' == false -> '' -> 0 and false -> 0 -> 0 == 0

Also remember

  • NaN is not equal to itself (NaN !== NaN).
  • Objects are compared by reference; two distinct objects are never == even if structurally identical.

Interview strategy

  • Give clear examples: null vs undefined, number/string coercion, object-to-primitive conversions.
  • Conclude by recommending === except when you have a specific reason for == and know the rules.

References: MDN: Equality comparisons and sameness

  1. “What does the new operator actually do?”

Why they’re asking

This checks that you know the runtime steps when constructing objects - it’s more than just ‘allocates an object’. Interviewers may then follow-up with ‘what if constructor returns an object?‘.

Short answer

“new performs roughly four steps: (1) create a new empty object, (2) set its internal [[Prototype]] to the constructor’s prototype, (3) call the constructor with ‘this’ bound to that new object and pass arguments, and (4) return the object returned by the constructor if it’s an object; otherwise return the newly created object.”

Example and edge case

function Person(name) {
  this.name = name;
  // return {a: 1}; // if you returned an object here, new Person() would resolve to that object
}

const x = new Person('Ben');
// x.__proto__ === Person.prototype

Interview strategy

  • Walk through the four steps clearly.
  • Mention that functions called without new are not constructors (unless they explicitly handle it), and that ES6 class constructors will throw if invoked without new.
  • If asked about simulating new: show a small helper:
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype);
  const result = Constructor.apply(obj, args);
  return typeof result === 'object' && result !== null ? result : obj;
}

References: MDN: new operator

Wrapping up: how to answer tricky JS questions in interviews

  • Start with a concise one-sentence summary.
  • Show a short code example that demonstrates the behavior.
  • Explain the “why” - mention spec-level reasons or engine concerns if relevant (e.g., TDZ, microtasks).
  • Call out edge cases and common pitfalls.
  • If you don’t recall a detail, be honest and say how you’d verify - e.g., open the console or check the spec/MDN - but still explain the parts you do know.

Further reading

Good luck - understand concepts, practice small examples, and you’ll be able to turn these “tricky” questions into opportunities to shine.

Back to Blog

Related Posts

View All Posts »

Debunking Myths: Tricky JavaScript Questions You Shouldn’t Fear

Tricky JavaScript interview questions often trigger anxiety - but they’re usually testing reasoning, not rote memorization. This article debunks common myths, explains why interviewers ask these questions, walks through concrete examples, and gives practical strategies to answer them confidently.