Skip to main content

JavaScript Interview Questions

Master these 31 carefully curated interview questions to ace your next JavaScript Interview Questions interview.

Quick Answer

JavaScript has 8 data types: String, Number, BigInt, Boolean, Undefined, Null, Symbol, and Object.

Detailed Explanation

Primitive types (String, Number, BigInt, Boolean, Undefined, Null, Symbol) are immutable and stored by value. Object is reference type and includes arrays, functions, dates, and regular expressions. typeof operator can check types, though typeof null returns 'object' (a known bug). ES6 added Symbol for unique identifiers and BigInt for arbitrarily large integers.

Quick Answer

var is function-scoped and hoisted; let is block-scoped and not hoisted; const is block-scoped and cannot be reassigned.

Detailed Explanation

var declarations are hoisted to the top of their function scope and initialized as undefined. let and const are block-scoped (within {}) and exist in a 'temporal dead zone' until declared. const requires initialization at declaration and prevents reassignment, but object/array contents can still be mutated. Best practice: use const by default, let when reassignment is needed, avoid var.

Quick Answer

Hoisting is JavaScript's behavior of moving declarations to the top of their scope before code execution.

Detailed Explanation

During compilation, JavaScript moves variable and function declarations to the top of their containing scope. Function declarations are fully hoisted (can be called before declaration). var variables are hoisted but initialized as undefined. let/const are hoisted but not initialized, creating a temporal dead zone. Class declarations are not hoisted. This is why you can call a function before its declaration in code.

Quick Answer

== performs type coercion before comparison; === compares both value and type without coercion.

Detailed Explanation

The == operator converts operands to the same type before comparing (e.g., '5' == 5 is true). The === operator checks both value and type strictly (e.g., '5' === 5 is false). Always prefer === to avoid unexpected coercion bugs. Notable: null == undefined is true, NaN !== NaN, and objects are compared by reference, not value.

Quick Answer

A closure is a function that retains access to its outer scope's variables even after the outer function has returned.

Detailed Explanation

When a function is created inside another function, it forms a closure over the outer function's variables. The inner function 'closes over' and remembers the environment in which it was created. This enables data privacy (module pattern), factory functions, and callback patterns. Example: function counter() { let count = 0; return () => ++count; } — the returned function remembers count.

Quick Answer

undefined means a variable has been declared but not assigned; null is an intentional assignment representing 'no value'.

Detailed Explanation

undefined is the default value of uninitialized variables, missing function parameters, and non-existent object properties. null is explicitly assigned to indicate 'no value' or 'empty'. typeof undefined is 'undefined'; typeof null is 'object' (a legacy bug). null == undefined is true (loose), but null === undefined is false (strict). Use null for intentional absence of value.

Quick Answer

The event loop is the mechanism that handles asynchronous callbacks by monitoring the call stack and task queues.

Detailed Explanation

JavaScript is single-threaded. The event loop continuously checks: (1) Is the call stack empty? (2) If yes, take the first callback from the microtask queue (Promises, queueMicrotask). (3) If microtask queue is empty, take from the macrotask queue (setTimeout, setInterval, I/O). Microtasks always execute before macrotasks. This is why Promise.then() runs before setTimeout(fn, 0). The browser also handles rendering between macrotasks.

Quick Answer

call invokes with arguments listed; apply invokes with arguments as array; bind returns a new function with bound this.

Detailed Explanation

func.call(thisArg, arg1, arg2) — immediately invokes with given this and individual arguments. func.apply(thisArg, [args]) — same but accepts arguments as an array. func.bind(thisArg, arg1) — returns a new function with this permanently bound (does not invoke immediately). Use call/apply for borrowing methods; bind for event handlers and callbacks where you need to preserve context.

Quick Answer

Promises represent a value that may be available now, later, or never. They have three states: pending, fulfilled, or rejected.

Detailed Explanation

A Promise is created with new Promise((resolve, reject) => {}). It starts as 'pending', then transitions to 'fulfilled' (resolve called) or 'rejected' (reject called). Chain .then() for success, .catch() for errors, .finally() for cleanup. Promise.all() waits for all to resolve; Promise.race() resolves with the first settled; Promise.allSettled() waits for all regardless of outcome. Async/await is syntactic sugar over Promises.

Quick Answer

Objects can inherit properties from other objects through the prototype chain, where each object has an internal [[Prototype]] link.

Detailed Explanation

Every JavaScript object has a hidden [[Prototype]] property linking to another object. When accessing a property, JavaScript first checks the object itself, then walks up the prototype chain. Object.create(proto) creates an object with proto as its prototype. Constructor functions set prototypes via .prototype property. ES6 classes are syntactic sugar over prototypal inheritance. The chain ends at Object.prototype, whose prototype is null.

Quick Answer

Shallow copy copies only the top-level properties; deep copy recursively copies all nested objects.

Detailed Explanation

Shallow copy methods: Object.assign(), spread operator {...obj}, Array.from(). These copy references for nested objects, so mutations in nested objects affect both copies. Deep copy methods: structuredClone() (modern), JSON.parse(JSON.stringify(obj)) (loses functions/dates), or libraries like lodash.cloneDeep(). structuredClone() handles circular references and most types but not functions or DOM nodes.

Quick Answer

Generators are functions that can pause and resume execution, yielding multiple values over time.

Detailed Explanation

Declared with function* syntax. Calling a generator returns an iterator. yield pauses execution and returns a value. Calling .next() resumes from where it paused. yield* delegates to another generator. Generators enable lazy evaluation, infinite sequences, and are the foundation of async/await (internally). They maintain their own execution context between yields.

Quick Answer

CommonJS uses require/module.exports (synchronous, Node.js); ES Modules use import/export (static, async, browser-native).

Detailed Explanation

CommonJS: require() is synchronous, modules are cached after first load, module.exports defines the export. ES Modules: import/export are statically analyzable (tree-shaking), loaded asynchronously, use strict mode by default, have live bindings (exports update automatically). Node.js supports both via .mjs extension or 'type: module' in package.json. ESM is the standard going forward.

Quick Answer

Proxy wraps an object to intercept and customize fundamental operations like property access, assignment, and function calls.

Detailed Explanation

new Proxy(target, handler) creates a proxy. The handler object defines 'traps' like get, set, has, deleteProperty, apply, construct. Used for validation (reject invalid values), logging/debugging, default values, data binding in frameworks (Vue 3 reactivity), and creating observable objects. Reflect API provides default behavior for each trap. Proxies are transparent — typeof proxy === typeof target.

Quick Answer

JavaScript uses mark-and-sweep garbage collection, automatically freeing memory for objects that are no longer reachable.

Detailed Explanation

The GC starts from 'roots' (global object, current call stack) and marks all reachable objects. Unreachable objects are collected. V8 uses generational GC: young generation (Scavenger, fast) and old generation (Mark-Sweep-Compact). Memory leaks occur from: forgotten timers/callbacks, closures retaining references, detached DOM nodes, and global variables. WeakMap/WeakSet allow values to be garbage collected when keys are no longer referenced.

Quick Answer

WeakMap and WeakSet hold weak references to objects, allowing garbage collection when no other references exist.

Detailed Explanation

WeakMap: keys must be objects, values can be anything. Keys are weakly referenced — if no other reference to the key exists, it's garbage collected along with its value. Not iterable, no size property. WeakSet: only stores objects, also weakly referenced. Use cases: private data storage, caching DOM node metadata, tracking object instances without preventing GC. Unlike Map/Set, they don't prevent memory leaks.

Quick Answer

Web Workers run JavaScript in background threads, enabling parallel execution without blocking the main thread.

Detailed Explanation

Workers run in separate threads with their own event loop and global scope (no DOM access). Communication via postMessage/onmessage (structured clone). Types: Dedicated Workers (one-to-one), Shared Workers (shared across tabs), Service Workers (proxy for network requests, offline support). Use for CPU-intensive tasks: image processing, data parsing, complex calculations. transferable objects avoid copying overhead.

Quick Answer

Tree shaking is a dead-code elimination technique that removes unused exports from the final bundle.

Detailed Explanation

Works with ES Modules because import/export are statically analyzable at build time. Bundlers (Webpack, Rollup, Vite) analyze the dependency graph, trace which exports are actually imported, and eliminate the rest. Requires: ES Module syntax, sideEffect-free code (or 'sideEffects: false' in package.json), production mode. CommonJS require() cannot be tree-shaken because it's dynamic. Write pure, side-effect-free modules for best results.

Quick Answer

Use Chrome DevTools Memory tab to take heap snapshots, compare allocations, and identify retained objects.

Detailed Explanation

Steps: (1) Open DevTools > Memory tab. (2) Take a heap snapshot, perform the leaking action, take another snapshot. (3) Compare snapshots to find growing objects. (4) Check 'Retainers' to see what's keeping objects alive. (5) Common causes: event listeners not removed, setInterval not cleared, closures capturing large scopes, detached DOM trees, global caches growing unbounded. (6) Fix by removing listeners in cleanup, using WeakRef/WeakMap, and clearing timers.

Quick Answer

Code splitting, lazy loading, tree shaking, minification, compression, and critical rendering path optimization.

Detailed Explanation

Strategies: (1) Code splitting with dynamic import() to load routes/features on demand. (2) Tree shaking to remove unused code. (3) Minify and compress (gzip/brotli). (4) Defer non-critical scripts. (5) Preload critical resources. (6) Use CDN for static assets. (7) Optimize images (WebP, lazy loading). (8) Reduce third-party scripts. (9) Use service workers for caching. (10) Analyze with Lighthouse and webpack-bundle-analyzer.

Quick Answer

Move the heavy computation to a Web Worker or break it into chunks using requestIdleCallback/setTimeout.

Detailed Explanation

The main thread is blocked because synchronous computation prevents the event loop from processing UI updates. Solutions: (1) Web Worker for true parallel processing. (2) Chunk processing with setTimeout(processChunk, 0) to yield to the browser between chunks. (3) requestIdleCallback for non-urgent work. (4) Virtual scrolling for large lists (only render visible items). (5) Pagination/infinite scroll to limit data. (6) Use streaming/incremental processing instead of loading everything at once.

Quick Answer

The output is 'string'. typeof 1 returns 'number' (a string), and typeof 'number' returns 'string'.

Detailed Explanation

Step by step: typeof 1 evaluates to the string 'number'. Then typeof 'number' evaluates to 'string' because 'number' is a string value. The typeof operator always returns a string. This is a common trick question testing understanding of the typeof operator's return type.

Quick Answer

Debounce delays function execution until after a specified wait time has elapsed since the last call.

Detailed Explanation

Implementation: function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } — Each call resets the timer. The function only executes after the user stops invoking it for 'delay' milliseconds. Used for search input, window resize, scroll handlers. Throttle is different — it guarantees execution at regular intervals.

Quick Answer

React maintains a lightweight JS representation of the DOM, diffs changes between renders, and applies minimal DOM updates.

Detailed Explanation

Process: (1) Component renders JSX which creates a virtual DOM tree (plain JS objects). (2) On state change, React creates a new virtual DOM tree. (3) React's reconciliation algorithm (Fiber) diffs the old and new trees. (4) It calculates the minimum set of DOM operations needed. (5) Batches and applies changes to the real DOM. (6) Fiber architecture enables prioritized, interruptible rendering. Keys help React identify which items changed in lists.

Quick Answer

eval() executes arbitrary code, creating XSS vulnerabilities, scope pollution, and performance issues.

Detailed Explanation

Risks: (1) Code injection — user input in eval() enables XSS attacks. (2) Scope access — eval can read/modify local variables. (3) Performance — prevents engine optimizations, no JIT compilation. (4) Debugging difficulty — eval'd code doesn't appear in stack traces properly. Alternatives: JSON.parse() for data, Function constructor for dynamic functions (still risky), template literals for string building, Map objects for dynamic property access.

Quick Answer

Temporal is a modern date/time API replacing the flawed Date object, offering immutable, timezone-aware date handling.

Detailed Explanation

The Date object has known issues: mutable, zero-indexed months, no timezone support, inconsistent parsing. Temporal provides: Temporal.PlainDate (date without time), Temporal.PlainTime, Temporal.ZonedDateTime (with timezone), Temporal.Duration, Temporal.Instant (exact point in time). All values are immutable. Arithmetic is built-in: date.add({ days: 5 }). Currently at Stage 3 in TC39. Polyfill available via @js-temporal/polyfill.

Quick Answer

structuredClone() creates deep copies of objects, handling circular references and most built-in types unlike JSON methods.

Detailed Explanation

structuredClone(value) performs a deep copy using the structured clone algorithm. Supports: Arrays, Objects, Maps, Sets, Dates, RegExp, Blob, File, ArrayBuffer, ImageData, circular references. Does NOT support: Functions, DOM nodes, Symbols, property descriptors (getters/setters). Advantages over JSON.parse(JSON.stringify()): handles circular refs, preserves Date/Map/Set types, supports binary data. Available in all modern browsers and Node.js 17+.

Quick Answer

Use IntersectionObserver to detect when a sentinel element enters the viewport, then fetch and append new data.

Detailed Explanation

Steps: (1) Render initial data. (2) Place a sentinel div at the bottom. (3) Create IntersectionObserver watching the sentinel. (4) When sentinel is visible, fetch next page. (5) Use virtual scrolling (react-window/react-virtualized) for lists with 1000+ items — only renders visible items. (6) Implement loading states and error handling. (7) Consider cursor-based pagination over offset for consistency. (8) Debounce rapid scroll events. (9) Add 'scroll to top' button for UX.

Quick Answer

Use a cache-first strategy with Map/WeakMap, stale-while-revalidate pattern, and TTL-based expiration.

Detailed Explanation

Implementation: (1) Create a cache Map with key = URL + params, value = { data, timestamp, ttl }. (2) On request, check cache first. If valid (not expired), return cached data. (3) If stale, return cached data AND fetch fresh data in background (stale-while-revalidate). (4) Use AbortController to cancel duplicate in-flight requests. (5) Set max cache size to prevent memory issues. (6) Clear cache on user logout. Libraries like SWR and React Query implement this pattern with additional features like deduplication and prefetching.

Quick Answer

Use AbortController to cancel stale requests, mutex locks, request deduplication, and optimistic UI with rollback.

Detailed Explanation

Race conditions occur when multiple async operations compete. Solutions: (1) AbortController: cancel previous fetch when new one starts. (2) Sequence ID: track latest request, ignore responses from older requests. (3) Mutex/semaphore: ensure only one operation runs at a time using a lock. (4) Debounce: delay execution until user stops typing. (5) Optimistic updates with rollback on failure. (6) Server-side idempotency keys for payment/mutation operations. (7) React 18's useTransition for UI state race conditions.

Ready to master JavaScript Interview Questions?

Start learning with our comprehensive course and practice these questions.