98.8% of websites use JavaScript. These 10 interview questions on event loop, async/await, closures, and type coercion separate top candidates from the rest.
JavaScript is the most-used programming language for 14 consecutive years. This guide distills the 10 questions interviewers ask most in 2026, covering single-threaded execution, the event loop, async patterns, ES6+ features, and higher-order functions. Each answer includes a runnable code snippet and an interview tip.
JavaScript runs on 98.8% of all websites and has topped the Stack Overflow Developer Survey for 14 straight years, relied on by 66% of professional developers (Stack Overflow, 2025). npm now hosts over 2 million packages (GitHub Octoverse, 2024). The language isn't slowing down.
Yet interviews expose a predictable gap. Most candidates know how to write JavaScript. Fewer can explain the execution model, the async story, or why certain patterns produce surprising results. These 10 questions target exactly that gap.
Each one shows up regularly at product companies and startups. Work through all 10, and you're ready for the core rounds at the junior-to-mid level.
Key Takeaways
JavaScript runs on 98.8% of websites and is used by 66% of professional developers (Stack Overflow, 2025)
It's single-threaded: the event loop handles async tasks without blocking the main thread
letandconstare block-scoped ES6 additions;varis function-scoped and always hoisted — useconstby defaultCallbacks started async JavaScript; Promises improved the model;
async/awaitmade it readableArrow functions don't have their own
this, which creates surprises in object methods and class callbacks
JavaScript runs on a single thread, processing one operation at a time on the call stack. This design eliminates the race conditions that complicate multi-threaded languages. According to the Stack Overflow 2025 survey, 66% of developers use JavaScript professionally, making runtime knowledge a baseline interview expectation rather than a bonus (Stack Overflow, 2025). Master these three concepts and you'll handle the toughest runtime questions with confidence.
JavaScript is single-threaded. It runs one statement at a time on a single call stack, with only one piece of code executing at any moment. This was a deliberate design choice. It keeps the runtime simple and prevents the locking and race conditions that plague multi-threaded languages.
console.log("First");
console.log("Second");
console.log("Third");
// Always prints in order: First → Second → ThirdSingle-threaded execution means heavy operations — computation, network requests — would freeze the page if run synchronously. The event loop exists precisely to handle this problem.
Interview tip: Mention Web Workers as a natural follow-up. They run JavaScript on background threads for CPU-heavy tasks without touching the main thread. The core execution model stays single-threaded regardless.
The event loop lets single-threaded JavaScript handle async work without blocking. When the engine hits an async task (a setTimeout, a fetch, or a DOM event), it offloads that work to the browser's Web APIs. Once the task completes, the callback lands in the task queue. The event loop runs a continuous check: is the call stack empty? If yes, it pulls the next callback off the queue and runs it.
console.log("Start");
setTimeout(() => {
console.log("Inside timeout"); // deferred to task queue
}, 0);
console.log("End");
// Output:
// Start
// End
// Inside timeoutEven a 0ms delay doesn't make setTimeout fire immediately. The callback waits until the call stack clears completely. "Inside timeout" always prints last.
Interview tip: Take it one level deeper by distinguishing microtasks from macrotasks. Promise.then callbacks sit in the microtask queue, which flushes completely before the next macrotask (like setTimeout) runs. Candidates who know this distinction stand out in runtime-focused rounds.
var is function-scoped, hoisted to the top of its function, and initialized as undefined during hoisting. let and const are block-scoped ES6 additions. Both are hoisted too, but not initialized, so accessing either before its declaration throws a ReferenceError (this is the temporal dead zone). const prevents reassignment but doesn't make objects or arrays immutable. The MDN guide on let covers the temporal dead zone behavior thoroughly.
var a = 1;
{
let b = 2; // block-scoped, invisible outside these braces
const c = 3; // block-scoped, cannot be reassigned
}
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes (undefined) | Yes (TDZ) | Yes (TDZ) |
| Reassignable | Yes | Yes | No |
| Redeclarable | Yes | No | No |
Interview tip: Default to const in modern code. Use let only when reassignment is required. Avoid var entirely in ES6+ codebases. Its function-scoping and hoisting behavior causes more surprises than it's worth.
JavaScript's async model evolved through three distinct patterns. Callbacks were the original approach, but nested async calls created deeply indented, hard-to-maintain code. Promises (ES6) flattened the structure into chainable operations. Then async/await (ES2017) added synchronous-style syntax built on top of Promises. Understanding this progression tells an interviewer whether you've followed the language's evolution or just learned its current syntax.
A callback is a function passed as an argument to another function, called once an operation finishes. They were JavaScript's original approach to async code, before Promises existed.
function fetchData(callback) {
setTimeout(() => {
callback("data received");
}, 1000);
}
fetchData((result) => {
console.log(result); // "data received"
});The problem with callbacks at scale is nesting. Each dependent async step has to live inside the previous callback, creating deeply indented code called "callback hell." Hard to read, hard to test, hard to maintain. This limitation drove the introduction of Promises in ES6.
Interview tip: Bring up callback hell without being asked. It signals you understand the historical progression, not just the current syntax. Interviewers notice candidates who connect why patterns exist to what problems they replaced.
async/await, introduced in ES2017, is the modern standard for async JavaScript. It builds on Promises but lets you write async logic in a sequential, synchronous-looking style. Easier to read and debug than chained .then() calls.
// Promise chain
function getUser() {
return fetch("/api/user")
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error(err));
}
// async/await equivalent
async function getUser() {
try {
const res = await fetch("/api/user");
const data = await res.json();
console.log(data);
} catch (err) {
console.error("Request failed:", err);
}
}await pauses execution inside the async function until the Promise resolves. It doesn't block the main thread. Error handling moves into standard try/catch blocks instead of .catch() chains.
Interview tip: Know that async functions always return a Promise. Even when you return a plain value, it's implicitly wrapped in Promise.resolve(value). This matters when callers chain .then() onto an async function's return value.
CommonJS (CJS) is Node.js's original module system: synchronous and designed for server-side file loading. ES Modules (ESM) are the JavaScript standard, built for browsers and modern bundlers, with static imports analyzed at parse time rather than runtime.
// CommonJS (Node.js legacy)
const fs = require("fs");
module.exports = { readFile };
// ES Modules (modern standard)
import fs from "fs";
export { readFile };The key practical difference is tree shaking. Because import/export statements are static, bundlers like Webpack and Vite can analyze the dependency graph at build time and strip unused code. CommonJS's dynamic require() makes this impossible, so unused modules stay in the bundle. The Node.js ESM documentation covers how to enable it in Node projects.
Interview tip: Node.js supports ESM natively via .mjs files or "type": "module" in package.json. Most modern projects use ESM on both client and server. If asked for the practical difference, tree shaking is the answer that earns points.
ES6 (released in 2015) added arrow functions, let/const, classes, Promises, template literals, and modules in a single release. According to W3Techs, JavaScript now runs on 98.8% of all tracked websites as of June 2026. ES6+ syntax is the baseline in virtually every production codebase, and these features appear in nearly every mid-level interview.
Pattern worth knowing: Among these 10 questions, Q7 (arrow functions and
this) and Q8 (type coercion) are the two where otherwise strong candidates most often lose points. Both look simple on the surface but have counterintuitive behavior that trips people up under pressure.
Arrow functions offer a shorter syntax and lexically bind this. They inherit this from the surrounding scope at definition time, rather than determining it dynamically at call time. Regular functions create their own this context depending on how they're called.
const obj = {
name: "Abhijeet",
greetArrow: () => {
console.log(this.name); // undefined: `this` is the outer scope (window/global)
},
greetRegular() {
console.log(this.name); // "Abhijeet": `this` is obj
},
};
obj.greetArrow(); // undefined
obj.greetRegular(); // "Abhijeet"Arrow functions also can't be used as constructors (no new), don't have an arguments object, and are always anonymous.
Interview tip: Use regular functions for object methods where this needs to refer to the object. Use arrow functions for callbacks and anywhere you want this inherited from the enclosing scope, like inside class methods.
Type coercion is JavaScript's automatic conversion of values between types during operations. It's why certain expressions evaluate to unexpected results, and it's a consistent source of subtle production bugs.
console.log(5 + 5 + "15"); // "1015"
// 5 + 5 = 10 (number), then 10 + "15" = "1015" (string concatenation)
console.log(5 + 15 - "15"); // 5
// 5 + 15 = 20 (number), then 20 - "15" = 5 (string coerced to number)
console.log(0 == false); // true (loose equality: both coerce to 0)
console.log(0 === false); // false (strict equality: no coercion)The == operator coerces types before comparing. === compares both value and type without any conversion.
Interview tip: Always use === in production code. Coercion with == produces results that are hard to reason about and easy to get wrong. Interviewers know this and will specifically probe it with tricky equality examples.
JavaScript is a programming language that runs inside browsers, where the host environment provides APIs like document, window, and fetch. Node.js is a runtime that lets the same language run on a server or your operating system, using the V8 engine, but without any browser APIs.
| Aspect | JavaScript (Browser) | Node.js |
|---|---|---|
| Environment | Browser | Server / OS |
| APIs | DOM, window, fetch | fs, http, process |
| Module system | ES Modules (native) | CommonJS (default) + ESM |
| Primary use | UI, interactions | APIs, CLIs, tooling |
The code is the same language. The difference is the environment and which APIs are available.
Interview tip: When asked about Node.js, mention its event-driven, non-blocking I/O model. That's what makes Node well-suited for high-concurrency workloads like real-time apps and API servers, handling thousands of connections without spinning up thousands of threads.
A higher-order function either accepts another function as an argument or returns a function as its result. They're the foundation of functional programming in JavaScript and show up constantly through built-ins like map, filter, and reduce.
// Custom higher-order function
function applyTwice(fn, value) {
return fn(fn(value));
}
const double = (x) => x * 2;
console.log(applyTwice(double, 3)); // 12 (3 → 6 → 12)
// Built-in HOFs you use daily
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter((n) => n % 2 === 0); // [2, 4]
const doubled = numbers.map((n) => n * 2); // [2, 4, 6, 8, 10]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15Higher-order functions enable function composition: building complex behavior by combining small, reusable pieces. That's the idea behind utility libraries like lodash and the functional patterns common in React codebases.
Interview tip: Be ready to implement map or filter from scratch. Interviewers often follow up with: "Can you write your own version of Array.prototype.map?" It's a clean test of whether you understand the concept or just the API.
The event loop is a continuous mechanism that checks whether JavaScript's call stack is empty. When it is, the loop moves the next pending callback from the task queue onto the stack for execution. It's what lets JavaScript handle timers, network calls, and DOM events without blocking the main thread, all while remaining single-threaded. The MDN event loop guide has a visual walkthrough.
=== is strict equality: it compares both value and type with no type conversion. == is loose equality: it coerces operands to the same type before comparing, which produces results like 0 == false evaluating to true. Use === everywhere in production code to avoid coercion-related bugs. The MDN equality reference documents all edge cases.
async/await is generally better for readability: it reads like synchronous code and handles errors with try/catch. Direct Promises with .then/.catch remain useful when coordinating parallel operations via Promise.all or Promise.race. Under the hood, async/await compiles to Promises, so both patterns are fully compatible. Most modern codebases default to async/await for sequential logic.
Arrow functions don't create their own this context. They inherit it from the lexical scope where they're defined. Inside an arrow function used as an object method, this refers to whatever this was in the surrounding scope (often window or undefined in strict mode), not the object. Use a regular function when you need this to refer to the calling context.
A closure is a function that retains access to its outer function's variables even after the outer function has finished executing. Every time you define a function inside another function, a closure is formed. Closures power counter factories, module patterns, and React hooks like useState, which rely on capturing state values across re-renders.
These 10 questions map to what interviewers consistently return to: the execution model, the async story, and modern ES6+ idioms. Get all 10 right, and you've handled the core of most junior-to-mid JavaScript rounds.
Work through each answer until you can explain it without looking. Then write each code snippet from memory. That's the fluency level the question is actually testing for.
Ready to go deeper? The JavaScript Coding Interview Questions guide covers algorithm and data structure problems, and the Senior Frontend Interview Topics guide covers what's expected at the senior level. For a full 50-question deep dive, see the complete JavaScript Interview Questions guide.