Ace your JavaScript interview with 50+ essential questions covering closures, async programming, and modern ES6+ features.
A closure is a function that has access to variables from its outer (enclosing) scope, even after the outer function has returned. Closures are useful for: data privacy (creating private variables), function factories, maintaining state in async operations, and implementing modules. They're fundamental to JavaScript's functional programming patterns.
"this" refers to the execution context and varies by how a function is called: (1) Global context: window/global, (2) Object method: the object, (3) Constructor: new instance, (4) Arrow functions: lexically inherited from parent, (5) call/apply/bind: explicitly set. Arrow functions are commonly used to preserve "this" in callbacks.
Hoisting moves variable and function declarations to the top of their scope during compilation. var declarations are hoisted and initialized as undefined. Function declarations are fully hoisted (callable before declaration). let/const are hoisted but NOT initialized (temporal dead zone — accessing them before declaration throws ReferenceError). Function expressions and arrow functions are not hoisted.
TDZ is the period between entering a block and the let/const declaration being initialized. During TDZ, accessing the variable throws ReferenceError even though it's been hoisted. This ensures variables are always initialized before use. TDZ applies to let, const, and class declarations. Unlike var which is initialized as undefined when hoisted.
var is function-scoped and hoisted (initialized as undefined). let and const are block-scoped and hoisted but not initialized (temporal dead zone). const requires initialization and can't be reassigned (but objects/arrays can be mutated). Best practice: use const by default, let when reassignment is needed, avoid var.
The event loop is JavaScript's concurrency model. It continuously checks: (1) Execute synchronous code in the call stack, (2) When stack is empty, check the microtask queue (Promises), (3) Then check the macrotask queue (setTimeout, DOM events). This allows non-blocking I/O despite JavaScript being single-threaded.
Promises represent eventual completion or failure of async operations. They have three states: pending, fulfilled, rejected. async/await is syntactic sugar over Promises - async functions return Promises, and await pauses execution until the Promise resolves. async/await makes async code look synchronous and easier to read, while still being non-blocking.
Synchronous: code executes line by line, blocking until each completes. Asynchronous: code initiates an operation and moves on, result handled via callback/Promise/async-await. JavaScript is single-threaded but handles async via the event loop. Async patterns: callbacks (pyramid of doom), Promises (chainable), async/await (readable). Choose async for I/O operations, network requests, timers.
JavaScript uses prototypal inheritance where objects can inherit directly from other objects. Every object has a [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf). When accessing a property, JavaScript looks up the prototype chain until it finds the property or reaches null. This differs from classical inheritance in languages like Java.
== (loose equality) performs type coercion before comparison, which can lead to unexpected results (e.g., "1" == 1 is true). === (strict equality) compares both value and type without coercion ("1" === 1 is false). Best practice: always use === to avoid subtle bugs from type coercion.
Higher-order functions either take functions as arguments or return functions. Examples: map, filter, reduce (take callbacks), function factories (return functions), decorators. They enable functional programming patterns, code reuse, and abstraction. Example: array.map(x => x * 2) - map is a higher-order function taking a callback.
WeakMap and WeakSet hold "weak" references to objects, allowing garbage collection when no other references exist. WeakMap keys must be objects; WeakSet values must be objects. Use cases: storing private data associated with objects, caching, tracking DOM nodes without preventing their cleanup. They're not iterable and have no size property.
Debouncing delays function execution until a pause in events (e.g., wait 300ms after user stops typing). Throttling ensures a function runs at most once per time interval. Use debouncing for search inputs, form validation. Use throttling for scroll/resize handlers, rate-limiting API calls. Both optimize performance by reducing function calls.
Modern engines (V8) use mark-and-sweep: starting from GC roots (global, stack), mark all reachable objects, then sweep unreachable ones. Generational collection: new objects in "young" generation (collected frequently, fast), survivors promoted to "old" generation (collected rarely). Common memory leaks: global variables, detached DOM nodes, closures holding references, forgotten event listeners.
Module pattern uses IIFE to create private scope and expose a public API. ES6 modules (import/export) are the standard: static imports (tree-shakeable), live bindings, strict mode by default, async loading with dynamic import(). CommonJS (require) is synchronous, copies values. ES6 modules are preferred for new code — use bundlers (Vite, Webpack) for browser compatibility.
Proxy wraps an object and intercepts fundamental operations (get, set, has, deleteProperty) via handler traps. Reflect provides the default behavior for those same operations. Use cases: validation, logging, observable objects (Vue 3's reactivity), API mocking, memoization. Proxy cannot intercept === comparison or typeof on the proxy itself.
Iterator: object with next() method returning {value, done}. Generator: function* that uses yield to produce values lazily, returns an iterator. Async generators combine both for async data streams. Use for: lazy evaluation, infinite sequences, custom iteration, async pipelines. Symbol.iterator makes any object iterable (usable in for...of). Underpins many async patterns.
Property descriptors control property behavior: value, writable (can be changed), enumerable (shows in for...in), configurable (can be redefined/deleted). Object.defineProperty sets descriptors explicitly. Object.freeze makes all properties non-writable and non-configurable (shallow). Used internally by Vue 2 for reactivity, class field decorators, and creating immutable APIs.
Object literal: {}, fastest and most common. Constructor function: new Foo(). Object.create(proto): sets prototype explicitly. class syntax: syntactic sugar over prototypal inheritance. Factory function: plain function returning object (avoids new, supports composition). Object.assign / spread: shallow clone. Each approach has trade-offs around inheritance, memory, and flexibility.
Practice with interactive quizzes and get instant feedback.
Start Free Practice