JavaScript Interview Questions
JavaScript interviews test your grasp of the language's core mechanics — scope, closures, asynchronous behavior, and the prototype chain — often with short coding tasks.
What JavaScript interviews cover
Scope & closures
Lexical scope, closures, hoisting, and the temporal dead zone.
Async & the event loop
Callbacks, promises, async/await, microtasks vs macrotasks.
this & prototypes
How this is bound, call/apply/bind, and prototypal inheritance.
Types & coercion
== vs ===, truthiness, and common coercion gotchas.
Sample JavaScript interview questions
- What is a closure, and give a practical use for one.What a strong answer covers
- Lexical scope and function bundled with its environment
- Memory retention of outer variables
- Practical use: data encapsulation (module pattern)
- Practical use: event handlers with preserved state
View a sample answer
A closure is a function that retains access to its lexical scope even when executed outside that scope. It occurs when a function is defined inside another function and references variables from the outer function. This allows data to persist and be private. A practical use is the module pattern for data encapsulation: for example, creating a counter function that increments a private variable and returns the current value without exposing the variable globally. Another use is in event handlers where you need to capture a specific state at the time of binding; without closures, the handler might see the last value of a loop variable. Closures commonly cause memory leaks if references to large objects are retained unnecessarily. In JavaScript, every function creates a closure, but we leverage them explicitly for data hiding and factory functions.
- Explain the event loop, microtasks, and macrotasks.What a strong answer covers
- Call stack, Web APIs, Task Queue, Microtask Queue
- Single-threaded, non-blocking concurrency model
- Microtasks: Promise.then, MutationObserver, queueMicrotask
- Macrotasks: setTimeout, setInterval, I/O, UI rendering
- Event loop priority: microtasks before next macrotask
View a sample answer
The event loop is how JavaScript handles asynchronous operations despite being single-threaded. It continuously checks the call stack, and when the stack is empty, it processes items from the microtask queue first, then one macrotask from the macrotask queue. Macrotasks include setTimeout, setInterval, and I/O callbacks. Microtasks include Promise.then/catch/finally, queueMicrotask, and MutationObserver. Between macrotasks, the browser may render UI. A common pitfall is that microtasks can starve the macrotask queue if they are recursively added, blocking rendering. For example, a setTimeout with 0ms delay is placed in the macrotask queue and will execute after the current script and all pending microtasks finish. Understanding this helps in predicting execution order, especially with async/await, where each await creates a microtask continuation.
- What's the difference between == and ===, and when is == acceptable?What a strong answer covers
- == performs type coercion, === checks strict equality without coercion
- Coercion rules can lead to unexpected results (e.g., '0' == false)
- When to use ==: intentional null/undefined check (obj == null)
- Best practice: always prefer === unless specifically wanting coercion
View a sample answer
The == operator compares values after performing type conversion (coercion), whereas === compares both value and type without conversion. For example, '5' == 5 is true, but '5' === 5 is false. The coercion rules of == are complex and often lead to bugs, such as '' == false being true. However, == is acceptable in one specific case: checking if a value is null or undefined in a single comparison, because null == undefined is true and they don't coerce with other values in that comparison (obj == null is true if obj is null or undefined). Outside of that, it's safer to use === to avoid implicit type conversions. Many codebases enforce === via linters like ESLint. The performance difference is negligible; correctness is the priority.
- How is `this` determined, and how do call/apply/bind change it?What a strong answer covers
- this determined by execution context (how function is called)
- Default: global object (window) or undefined in strict mode
- Implicit binding: method call (obj.func())
- Explicit binding: call, apply, bind
- Arrow functions: lexical this from enclosing scope
View a sample answer
The value of `this` is determined by how a function is called, not where it is defined. In a regular function call (func()), `this` refers to the global object (window in browsers) or undefined in strict mode. In a method call (obj.func()), `this` is the object before the dot. With `new`, `this` refers to the newly created instance. `call` and `apply` immediately invoke the function with a specified `this` and arguments (`call` takes a list, `apply` takes an array). `bind` returns a new function with a fixed `this` that cannot be changed later. Arrow functions do not have their own `this`; they inherit it from the surrounding lexical scope. A common mistake is losing `this` when passing a method as a callback, e.g., `setTimeout(obj.method, 1000)` where `this` becomes the global object. Using `bind` or an arrow function in the callback resolves this.
- Implement a debounce (or throttle) function.What a strong answer covers
- Debounce: delays execution until after a quiet period
- Throttle: limits execution to once per interval
- Implementation using setTimeout and closures
- Use cases: input validation (debounce), scroll/resize (throttle)
- Leading vs trailing edge variants
View a sample answer
Debounce and throttle are two techniques to control how often a function is executed, especially for performance-sensitive events like typing, resizing, or scrolling. Debouncing ensures that a function is only called after a certain amount of time has passed since the last invocation. This is useful for auto-saving or search suggestions where you want to wait until the user stops typing. Throttling ensures a function is called at most once every specified interval, which is better for continuous events like scroll or mouse move to prevent excessive updates. Both use closures to store timers and state. A common pitfall is confusing the two; debounce delays execution, throttle caps frequency. Also, be aware of leading vs trailing invocation: immediate debounce fires on the first call, then suppresses subsequent calls; throttle usually fires on the leading edge by default but can be configured.
Reference solutionjavascript // Debounce: returns a function that delays invoking func until after wait milliseconds // have elapsed since the last invocation. If immediate is true, triggers on the leading edge. function debounce(func, wait, immediate = false) { let timeout; return function executedFunction(...args) { const context = this; const later = () => { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // Throttle: returns a function that ensures func is called at most once every wait milliseconds. function throttle(func, wait, options = { leading: true, trailing: true }) { let context, args, result; let timeout = null; let previous = 0; const later = () => { previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function(...args) { const now = Date.now(); if (!previous && options.leading === false) previous = now; const remaining = wait - (now - previous); context = this; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; } - What's the difference between var, let, and const?What a strong answer covers
- var: function-scoped, hoisted, can be redeclared
- let: block-scoped, hoisted but not initialized (TDZ)
- const: block-scoped, hoisted but not initialized, cannot be reassigned
- Redeclaration: var allowed, let/const not in same scope
- Best practice: use const by default, let for reassignment, avoid var
View a sample answer
The primary differences are scoping and hoisting behavior. `var` is function-scoped, meaning it is visible throughout the entire function regardless of block (e.g., if/for blocks), and it is hoisted to the top of its scope and initialized with `undefined`. `let` and `const` are block-scoped, meaning they exist only within the nearest enclosing block. They are hoisted but not initialized, resulting in a Temporal Dead Zone (TDZ) from the start of the block until the declaration, where accessing them throws a ReferenceError. `const` additionally prevents reassignment of the variable binding (though object properties can still be mutated). `var` allows redeclaration in the same scope, while `let` and `const` do not. The modern best practice is to use `const` by default and only use `let` when you need to reassign the variable. Avoid `var` because its function scoping and lack of block scoping often lead to bugs, especially in loops and closures.
How to prepare
- Be able to predict the output of tricky snippets (closures in loops, async ordering).
- Practice implementing debounce, throttle, and a simple promise from scratch.
- Understand the event loop well enough to explain async ordering precisely.
- Know the modern features (let/const, arrow functions, destructuring, modules) and why they exist.
Frequently asked questions
Is JavaScript still asked separately from React?
Yes — many interviews include a pure-JavaScript round on closures, async, and `this`, independent of any framework.
Do I need to know TypeScript?
Increasingly yes for frontend roles, but core JavaScript mechanics are still the foundation interviewers test.
What JavaScript topics come up most?
Closures, the event loop and promises, `this` binding, and implementing utilities like debounce or throttle.
How can I practice JavaScript interviews?
Predict snippet outputs and implement small utilities under time pressure. Offersly can generate JavaScript questions tailored to your resume.
Practice JavaScript questions with instant AI feedback
Upload your resume, get a personalized mock interview, and see exactly what to improve — free to start.