Javascript Console Interview Questions
Javascript Console Interview Questions
Interview Explanation: In an interview say: "This is a well-known bug in JavaScript that has existed since its first
version and was never fixed for backward-compatibility reasons. null is a primitive value that represents the intentional
absence of any object value, but typeof null incorrectly returns 'object'. The correct way to check for null is strict
equality: value === null. Never rely on typeof to detect null."
Interview Explanation: In an interview say: "This is a floating-point precision issue that exists in virtually every
programming language, not just JavaScript. JS uses the IEEE 754 double-precision format, so 0.1 + 0.2 actually
evaluates to 0.30000000000000004 due to binary representation limits. The correct way to compare decimals is to
check if the absolute difference is smaller than a small epsilon: [Link](a - b) < [Link], or use .toFixed()
when displaying values."
Interview Explanation: In an interview say: "JavaScript's + operator is overloaded — it does both addition and string
concatenation. When JS encounters a number + string, it converts the number to a string and concatenates. So 1 + '2'
becomes '12', and then '12' + 3 becomes '123'. The key rule to remember is: once a string enters a + chain, everything
after it gets concatenated as a string, not added numerically."
Interview Explanation: In an interview say: "When the + operator is used with arrays, JavaScript calls toString() on
each array first. An empty array's toString() returns an empty string. So [] + [] becomes '' + '', which is an empty string.
This demonstrates how JavaScript's type coercion can produce surprising results with non-primitive values."
Interview Explanation: In an interview say: "When an object is coerced to a string, its toString() method returns
'[object Object]'. An empty array [] becomes an empty string. So {} + [] is '[object Object]' + '' = '[object Object]'. One
important caveat: if {} appears at the start of a statement (not inside an expression), JS may parse it as an empty block
rather than an object literal, and the result could differ — which is why context matters."
Interview Explanation: In an interview say: "typeof undefined returns the string 'undefined'. This is one of the safe
uses of typeof — unlike null, checking typeof on an undeclared variable won't throw a ReferenceError, it will just return
'undefined'. This makes typeof a useful guard: if(typeof myVar !== 'undefined') is safe even if myVar was never
declared."
Interview Explanation: In an interview say: "The double-bang (!!) is a common JavaScript idiom for converting any
value to its boolean equivalent. The first ! negates the value (null is falsy, so !null is true), and the second ! negates
again to give the actual boolean (false). This is equivalent to Boolean(null). It's widely used in React to conditionally
render: {!!user && } to avoid rendering 0 or null."
Interview Explanation: In an interview say: "0 is one of JavaScript's falsy values, so !!0 is false. The eight falsy values
in JavaScript are: false, 0, -0, 0n (BigInt zero), '' (empty string), null, undefined, and NaN. Everything else is truthy —
including '0' (a non-empty string), [] (empty array), and {} (empty object). Knowing this list is essential for writing reliable
conditional checks."
Interview Explanation: In an interview say: "NaN is the only value in JavaScript — and in IEEE 754 floating-point
math — that is not equal to itself. This is by design: NaN means 'the result of an invalid or undefined mathematical
operation,' and two invalid operations don't have to mean the same thing. The reliable way to check for NaN is
[Link](value). Avoid the older global isNaN() function because it coerces its argument first: isNaN('hello')
returns true, while [Link]('hello') correctly returns false."
Answer: Variable and function declarations are moved to the top of their scope before execution.
Interview Explanation: In an interview say: "Hoisting is JavaScript's behavior of processing declarations before
executing any code. var declarations are hoisted and initialized to undefined, so you can reference them before their
line without a ReferenceError — but the value will be undefined. Function declarations are fully hoisted, meaning you
can call them before they appear in the code. let and const are also hoisted but placed in a 'Temporal Dead Zone' —
accessing them before their declaration line throws a ReferenceError. This is why best practice is to always declare
variables at the top of their scope."
Answer: The period between entering a scope and the let/const declaration being initialized.
Interview Explanation: In an interview say: "The TDZ is the region at the top of a block scope where let and const
variables exist (because they are hoisted) but cannot be accessed yet. Trying to read or write them before their
declaration line throws a ReferenceError. This behavior is intentional — it encourages declaring variables before use
and eliminates a whole class of bugs that were common with var hoisting. The TDZ ends the moment the JavaScript
engine reaches the let or const declaration line."
12. var x=1; function foo(){ [Link](x); var x=2; } foo() → Output?
Interview Explanation: In an interview say: "This is a classic hoisting trap. Inside foo(), var x is hoisted to the top of
the function scope and initialized to undefined. So the function effectively executes as: var x; [Link](x); x=2;. The
[Link] runs before the assignment, so it prints undefined. This is not a ReferenceError because x does exist in
scope — it's just not yet assigned. This is exactly why many style guides prefer let and const: they would throw a
ReferenceError here instead of silently printing undefined."
Answer: A function that retains access to its outer scope's variables even after the outer function has returned.
Interview Explanation: In an interview say: "A closure is created every time a function is defined inside another
function. The inner function 'closes over' the variables of the outer scope. A practical example is a counter factory:
function makeCounter(){ let count=0; return ()=>++count; }. Each call to makeCounter() creates a new, independent
count variable that only the returned function can access. Closures are the foundation of patterns like data privacy,
factory functions, memoization, and React hooks like useState, which use closures internally to keep state between
renders."
Answer: Output: 3, 3, 3
Interview Explanation: In an interview say: "This is one of the most famous JavaScript interview questions. Because
var is function-scoped (not block-scoped), there is only one shared i variable across all iterations. By the time the
setTimeout callbacks run — after the call stack is clear — the loop has already finished and i is 3. All three callbacks
close over the same i, which is 3. The fix is to replace var with let, which creates a new block-scoped binding for each
iteration, so each callback captures its own copy of i."
Answer: Replace var with let: for(let i=0; i<3; i++){ setTimeout(()=>[Link](i), 0) }
Interview Explanation: In an interview say: "The simplest fix is using let instead of var. let is block-scoped, so each
loop iteration creates a fresh binding of i. Each callback closes over its own independent i. An older ES5 fix was to use
an IIFE: setTimeout((function(i){ return ()=>[Link](i); })(i), 0) — which forces a new scope per iteration by
immediately invoking a function with the current i value. The let solution is cleaner and is the modern standard."
Answer: == uses type coercion before comparing. === compares value AND type without coercion.
Interview Explanation: In an interview say: "The == operator is called the abstract equality operator. It converts
(coerces) both operands to the same type before comparing, which can produce surprising results: 0 == false is true, ''
== false is true, null == undefined is true. The === operator is the strict equality operator — it never coerces, so both
the value and the type must match. Best practice is to always use === unless you have a deliberate reason to use ==,
such as null == undefined checks where you want to catch both null and undefined with one expression."
Interview Explanation: In an interview say: "This goes through JavaScript's abstract equality coercion algorithm. false
is converted to 0. [] is converted to a primitive — first to an empty string '', then to 0. So the comparison becomes 0 ==
0, which is true. This is a great example of why == can be unpredictable and why you should use === in production
code. This kind of coercion behavior is sometimes called the 'Abstract Equality Comparison Algorithm' and is fully
specified in the ECMAScript spec."
Answer: undefined = declared but never assigned. null = intentionally set to 'no value'.
Interview Explanation: In an interview say: "Both represent 'no value', but they have different semantic meanings.
undefined is what JavaScript assigns automatically when a variable is declared but not given a value, or when a
function doesn't explicitly return. null is a value you set deliberately to signal 'this variable intentionally has no object'. In
practice: if a function returns null it usually means 'I looked for something and found nothing', while undefined often
means 'this was never set up'. One gotcha: typeof null is 'object' (a historical bug), while typeof undefined is
'undefined'."
Answer: Attaching one event listener to a parent element to handle events from its children.
Interview Explanation: In an interview say: "Event delegation takes advantage of event bubbling. Instead of attaching
a click listener to every list item, you attach one listener to the parent ul and use [Link] to identify which child was
clicked. This has two major benefits: first, performance — one listener instead of potentially hundreds; second, it works
automatically for dynamically added children because the parent listener already exists. This pattern is used internally
by frameworks like jQuery's .on() and by React's synthetic event system."
Answer: An event on a child element propagates upward through all ancestor elements.
Interview Explanation: In an interview say: "When you click a button inside a div inside a section, the click event first
fires on the button (the target), then bubbles up to the div, then the section, then the body, then the document. Each
ancestor's click listeners fire in order. You can stop this with [Link](). Bubbling is what makes event
delegation possible. Note: not all events bubble — focus, blur, and scroll don't bubble by default, which is why focusin
and focusout were introduced as bubbling alternatives."
Answer: The event travels DOWN from the document root to the target before bubbling back up.
Interview Explanation: In an interview say: "The full event flow has three phases: capturing (top-down), target (at the
element), and bubbling (bottom-up). By default, addEventListener registers listeners for the bubbling phase. Passing
true as the third argument registers a listener for the capturing phase: [Link]('click', fn, true). Capturing
listeners fire before bubbling listeners. In practice, capturing is rarely needed, but it's useful when you need to intercept
an event before it reaches the target — for example, in some drag-and-drop implementations."
Answer: All three set 'this'. call passes args individually. apply passes args as array. bind returns a new function.
Interview Explanation: In an interview say: "All three let you explicitly control what this refers to inside a function. call
and apply invoke the function immediately — the difference is how you pass arguments: call takes them as a
comma-separated list, apply takes an array, which is useful when you already have arguments in an array. bind doesn't
invoke the function; instead it returns a new function with this permanently set to the provided value. bind is commonly
used in React class components to bind event handlers to the component instance, and in partial application patterns."
Answer: Arrow functions have no own 'this'. They inherit 'this' from the surrounding lexical scope.
Interview Explanation: In an interview say: "Regular functions define their own this based on how they are called —
as a method, as a constructor, or standalone. Arrow functions are different: they don't have their own this binding at all.
They capture this from the enclosing lexical context at the time the arrow function is defined. This makes arrow
functions ideal for callbacks and event handlers inside class methods where you want this to refer to the class
instance, not the event target or undefined. The rule of thumb: use arrow functions when you want to 'borrow' the outer
this, use regular functions when you need a dynamic this."
Interview Explanation: In an interview say: "This is a subtle gotcha. [Link] passes three arguments to the
callback: the current value, the index, and the array. [Link] receives all three — but parseInt's second
argument is the radix. So the calls are: parseInt(1, 0) which treats 0 as base 10 (returns 1), parseInt(2, 1) which has an
invalid radix of 1 (returns NaN), and parseInt(3, 2) which tries to parse '3' in base 2 — but 3 is not a valid binary digit
(returns NaN). The fix is to use an explicit wrapper: .map(x => parseInt(x)) or .map(Number)."
Interview Explanation: In an interview say: "Before Promises, asynchronous operations were handled with nested
callbacks, leading to 'callback hell' — deeply nested, hard-to-read code. A Promise represents the eventual result of an
async operation and lets you chain .then() for success and .catch() for errors, keeping code flat and readable.
Promises have three states: pending (still waiting), fulfilled (completed successfully), and rejected (failed). They are the
foundation of async/await syntax, which is just syntactic sugar on top of Promises."
Answer: [Link]: waits for all (fails fast on any rejection). [Link]: resolves/rejects with the first settled
Promise.
Interview Explanation: In an interview say: "[Link] takes an array of Promises and returns a single Promise that
resolves when all of them resolve, with an array of their results. If any one Promise rejects, [Link] immediately
rejects — this is called 'fail-fast'. [Link] also takes an array but resolves or rejects as soon as the first Promise
settles, ignoring the rest. A practical use for [Link] is implementing a timeout: race a fetch against a Promise
that rejects after N milliseconds. Also worth mentioning: [Link] (waits for all regardless of rejection) and
[Link] (resolves on first success)."
Answer: async/await is syntactic sugar over Promises, making async code read like synchronous code.
Interview Explanation: In an interview say: "An async function always returns a Promise. Inside it, the await keyword
pauses execution of that function until the awaited Promise settles, then resumes with the resolved value — without
blocking the main thread. Error handling is done with try/catch instead of .catch(), which many developers find more
readable. Under the hood, async/await is compiled down to Promise chains, so they are equivalent in power. One
common interview pitfall: if you await multiple independent Promises sequentially, you serialize them unnecessarily.
For parallel execution, use await [Link]([p1, p2]) instead of awaiting each one on its own line."
Interview Explanation: In an interview say: "This tests knowledge of the JavaScript execution model. Synchronous
code always runs first, so 'start' and 'end' print immediately. Then the engine processes the microtask queue —
Promise callbacks live here — before processing the macrotask queue, where setTimeout callbacks live. So 'promise'
prints before 'timeout' even though both are scheduled with zero delay. The order is: synchronous code → microtask
queue (Promises, queueMicrotask) → macrotask queue (setTimeout, setInterval, I/O). This distinction is critical for
understanding how the event loop works."
Answer: The mechanism that allows JS to perform non-blocking async operations despite being single-threaded.
Interview Explanation: In an interview say: "JavaScript has a single call stack — only one thing runs at a time. When
the stack is empty, the event loop checks the task queues and pushes the next task onto the stack. The flow is:
synchronous code fills the call stack and runs to completion; async tasks (setTimeout, fetch, etc.) are handled by Web
APIs or the Node runtime in the background; when they complete, their callbacks are placed in the task queue; the
event loop picks them up only when the call stack is empty. The microtask queue (Promises) is checked and fully
drained after every task, before the next macrotask runs."
Answer: Microtasks (Promises) run after every task before the next macrotask (setTimeout, setInterval).
Interview Explanation: In an interview say: "There are two task queues: the microtask queue and the macrotask
(task) queue. After every single macrotask, the engine completely drains the microtask queue before picking up the
next macrotask. Microtasks include: Promise callbacks (.then/.catch/.finally), queueMicrotask(), and MutationObserver
callbacks. Macrotasks include: setTimeout, setInterval, setImmediate (Node), and I/O events. A practical implication: if
a microtask keeps adding more microtasks, the macrotask queue will be starved and the UI will freeze — this is called
'microtask queue flooding'."
Answer: Objects inherit properties/methods from other objects through a prototype chain.
Interview Explanation: In an interview say: "In JavaScript, every object has an internal [[Prototype]] link (accessible
as __proto__ or via [Link]()). When you access a property that doesn't exist on an object, JS
automatically walks up the prototype chain looking for it. For example, arrays have methods like .push() and .map()
because [Link] has them — every array's prototype links to [Link]. This is different from classical
inheritance in Java or C++, where classes define blueprints. In JS, objects inherit from objects directly. ES6 class
syntax is purely syntactic sugar over this prototype system."
Answer: Creates a new object with the specified object as its prototype.
Interview Explanation: In an interview say: "[Link](proto) is the most explicit way to set up prototypal
inheritance. The new object's [[Prototype]] is set to proto, so it inherits all of proto's properties. Passing null creates an
object with no prototype at all — useful for pure hash maps with no inherited properties. This is different from the new
keyword, which sets up inheritance via a constructor function's .prototype property. [Link] gives you direct,
clean control over the prototype chain without needing constructors or classes."
Interview Explanation: In an interview say: "typeof returns 'function' for any callable object. Functions are technically
objects in JavaScript — they can have properties and methods — but typeof distinguishes them from plain objects. The
full list of possible typeof results is: 'undefined', 'boolean', 'number', 'bigint', 'string', 'symbol', 'object' (for objects AND
null), and 'function'. There is no 'array' type — typeof [] returns 'object', which is why you should always use
[Link]() to check for arrays."
Interview Explanation: In an interview say: "Higher-order functions are a cornerstone of functional programming in
JavaScript. .map(), .filter(), and .reduce() are the most commonly used — they take a callback function and apply it to
each element. Writing your own HOF is equally important in interviews: a debounce or throttle function, for example,
takes a function as input and returns a new, enhanced function. HOFs enable powerful patterns like function
composition, currying, and decorators. They make code more declarative and reusable."
Answer: map() returns a new array of transformed values. forEach() returns undefined and is used for side effects.
Interview Explanation: In an interview say: "map() creates and returns a new array by applying the callback to every
element — the original array is unchanged. forEach() is for performing side effects (like logging, updating the DOM, or
pushing to an external array) and always returns undefined. A common mistake is trying to chain .filter() or .find() after
.forEach() — this won't work because forEach returns undefined. Use map when you need a transformed result; use
forEach when you only care about the side effect and don't need a new array."
Answer: Reduces an array to a single accumulated value by applying a function to each element.
Interview Explanation: In an interview say: "reduce() is the most powerful and flexible array method. It takes a
callback that receives the accumulator and the current element, plus an initial value. You can implement map and filter
using reduce. Beyond simple sums, reduce is used for: grouping items by key, flattening nested arrays (before flat()
existed), counting occurrences, building lookup objects from arrays, and implementing function pipelines. In interviews,
showing a non-trivial reduce — like grouping an array of people by age group — demonstrates strong functional
programming understanding."
Interview Explanation: In an interview say: "The spread operator has two main uses. For arrays: it expands elements,
making it easy to clone ([...arr]), merge ([...arr1, ...arr2]), or pass array items as function arguments
([Link](...nums)). For objects: it copies enumerable own properties, making shallow copies or merging objects
({...obj1, ...obj2} — later keys overwrite earlier ones). One important caveat: spread does a shallow copy, so nested
objects are still shared by reference. Don't confuse spread with rest parameters, which look the same (...args) but
collect multiple arguments into an array instead."
Answer: Unpacking values from arrays or properties from objects into distinct variables.
Interview Explanation: In an interview say: "Destructuring makes code much cleaner when working with complex data
structures. Array destructuring uses position: const [a, b] = [1, 2]. Object destructuring uses keys: const { name, age } =
person. You can rename: const { name: userName } = person. You can set defaults: const { x = 0 } = {}. You can skip
elements in arrays: const [,, third] = [1,2,3]. Nested destructuring: const { address: { city } } = user. Function parameter
destructuring is very common in React: function Component({ title, onClick }). It's also used extensively with Promise
destructuring and API responses."
Answer: Output: 2
Interview Explanation: In an interview say: "This demonstrates that objects in JavaScript are assigned and passed by
reference, not by value. When you do const b = a, you're not creating a copy of the object — you're copying the
reference (memory address) that points to the same object. So a and b point to the exact same object in memory, and
changing b.x changes the only existing object, which a also sees. To create an independent copy, use the spread
operator: const b = {...a} for a shallow clone, or structuredClone(a) for a deep clone."
40. What is the difference between a shallow copy and a deep copy?
Answer: Shallow copy: top-level values copied, nested objects still shared. Deep copy: everything recursively cloned.
Interview Explanation: In an interview say: "A shallow copy creates a new object and copies the top-level properties.
But if a property's value is itself an object (nested), both the original and copy still point to that same nested object in
memory — so changing the nested object affects both. Methods for shallow copy: spread operator, [Link](),
[Link](). A deep copy recursively clones all levels. Methods for deep copy: structuredClone() (modern standard,
handles most types), [Link]([Link](obj)) (fast but loses functions, undefined, Dates become strings, and
can't handle circular references). Libraries like Lodash's _.cloneDeep() handle edge cases robustly."
Answer: Safely access nested properties, returning undefined instead of throwing if any part is null/undefined.
Interview Explanation: In an interview say: "Before optional chaining, accessing deeply nested data required
defensive code like: user && [Link] && [Link]. Optional chaining shortens this to:
user?.address?.city. If user is null or undefined, the expression short-circuits and returns undefined instead of throwing
a TypeError. It also works with method calls (user?.getAddress()) and array access (users?.[0]). It's particularly
valuable when working with API responses where fields may or may not be present, or when accessing optional
configuration objects."
42. What is nullish coalescing (??) and when do you use it?
Answer: Returns the right-hand operand only when the left-hand side is null or undefined.
Interview Explanation: In an interview say: "The ?? operator fills a gap that || couldn't properly address. || returns the
right side for any falsy value — including 0, false, and empty string, which are often legitimate values. ?? only triggers
for the two 'absent' values: null and undefined. For example, setting a default port: const port = [Link] ?? 3000. If
[Link] is 0 (valid port), || would incorrectly fall back to 3000, but ?? correctly keeps 0. Use ?? when 0, false, or '' are
valid values that should not trigger the fallback."
Answer: || falls back for any falsy value. ?? falls back only for null or undefined.
Interview Explanation: In an interview say: "This is an important practical distinction. The || operator checks for
truthiness: if the left side is any falsy value (0, '', false, null, undefined, NaN), it returns the right side. This can cause
bugs when 0 or empty string are valid inputs. The ?? operator only checks for null and undefined — the two values that
genuinely mean 'no value was provided'. Rule of thumb: use ?? for default values when dealing with user input,
configuration, or API data where 0 and '' are valid. Use || when you want to fall back on any falsy value."
Interview Explanation: In an interview say: "Currying converts a function f(a, b, c) into f(a)(b)(c). Each call returns a
new function waiting for the next argument. This enables partial application — pre-filling some arguments and getting a
specialized function back. For example: const multiply = a => b => a * b; const double = multiply(2); double(5) === 10.
Currying is fundamental in functional programming and appears in utility libraries like Ramda and lodash/fp. In
real-world frontend code, currying is used for creating event handlers with pre-baked configuration, like: const
handleChange = field => event => setState({ [field]: [Link] })."
Answer: Caching the return value of a function for a given input so it doesn't recompute on repeated calls.
Interview Explanation: In an interview say: "Memoization is an optimization technique — a specific form of caching. A
memoized function stores previously computed results in a cache (usually an object or Map), and on subsequent calls
with the same arguments, returns the cached result immediately instead of recomputing. It's a trade-off: you use more
memory to save computation time. In React, useMemo and useCallback are built-in hooks for memoization. In
algorithm interviews, memoizing recursive functions like Fibonacci transforms exponential O(2^n) time complexity to
linear O(n). The key: only memoize pure functions — functions with the same input always producing the same output."
Interview Explanation: In an interview say: "Debouncing ensures a function only runs after the user has stopped
triggering it for a given delay. The classic use case is a search input: you don't want to fire an API call on every
keystroke; instead, wait until the user pauses typing for, say, 300ms. Implementation: clear the previous timer and set a
new one on every call — only the last timer actually fires. debounce(fn, 300) — if called again within 300ms, the timer
resets. This is different from throttling: debounce waits for a pause, throttle allows execution at a maximum frequency.
Libraries like Lodash provide _.debounce()."
Answer: Limit a function to execute at most once per specified time interval.
Interview Explanation: In an interview say: "Throttling guarantees a function is called no more than once in a given
time window, regardless of how many times it's triggered. It's ideal for events that fire continuously: scroll, resize,
mousemove. For example, throttling a scroll handler to run at most once per 200ms dramatically reduces the number
of DOM updates and improves performance. The difference from debouncing: throttle runs regularly at the maximum
allowed rate (like a controlled drip), while debounce waits for silence before running. Both are essential for
performance optimization in frontend development."
Interview Explanation: In an interview say: "Boolean is a function that converts any value to true or false. When
passed to filter, it acts as a predicate that keeps only truthy values. In this case all three elements are truthy numbers,
so the result is [1,2,3]. The real power is with mixed arrays: [0, 1, '', 'hello', null, 42, undefined, false].filter(Boolean)
returns [1, 'hello', 42] — all the truthy values. This is a very common pattern in React for filtering out empty/null values
from an array before rendering, and for removing falsy elements from data received from an API."
Interview Explanation: In an interview say: "Arrays are objects, and objects are compared by reference in JavaScript,
not by value. [1] == [1] creates two separate arrays at different memory addresses, so they are not the same reference
— even though they contain the same data. This is true for all objects and arrays: {} == {} is also false. To compare
array or object contents, you need to either [Link] both sides (works for simple cases), or use a deep equality
function like Lodash's _.isEqual() for reliable structural comparison."
Answer: slice() returns a new array without modifying the original. splice() mutates the original array.
Interview Explanation: In an interview say: "slice(start, end) returns a new shallow copy of a portion of an array
between the given indices — it never changes the original. splice(start, deleteCount, ...items) is a Swiss army knife: it
removes elements from start, optionally inserts new items, and returns the removed elements — and it modifies the
original array in place. Memory aid: sliCe = Creates a new copy. spliCe = Changes the original. Common interview
question: how do you remove an element from an array? [Link](index, 1) removes one element at that index in
place."
Interview Explanation: In an interview say: "Ironically, NaN — which stands for 'Not a Number' — has typeof
'number'. This is because NaN is still part of the number type in the IEEE 754 specification; it just represents an invalid
numeric result. typeof alone cannot reliably detect NaN. The correct check is [Link](value), which returns true
only for the actual NaN value. Don't use the global isNaN() function — it first converts its argument to a number, so
isNaN('hello') returns true (because Number('hello') is NaN), which is misleading."
Interview Explanation: In an interview say: "An IIFE is written as (function(){ ... })() or (() => { ... })(). The outer
parentheses turn the function declaration into an expression, and the final () invoke it immediately. The key benefit is
scope isolation: variables declared inside an IIFE are local to it and don't pollute the global scope. Before ES6
modules, IIFEs were the primary way to encapsulate code in libraries and avoid naming conflicts. jQuery's source code
is wrapped in an IIFE. Today, ES modules provide better isolation, but IIFEs still appear in bundled output from tools
like Webpack and Rollup."
53. What is the difference between function declaration and function expression?
Answer: Declarations are fully hoisted. Expressions are only hoisted as an uninitialized variable.
Interview Explanation: In an interview say: "A function declaration: function foo(){} — is hoisted completely, meaning
you can call it before its definition in the code. A function expression: const bar = function(){} or const bar = ()=>{} —
the variable is hoisted (for var, as undefined; for let/const, into TDZ), but the function value is not assigned until that line
is reached. Calling bar() before its definition throws a TypeError (var) or ReferenceError (let/const). In practice, prefer
function expressions with const for most cases since they prevent accidental use-before-definition bugs."
Interview Explanation: In an interview say: "JavaScript evaluates this left to right: 1 < 2 evaluates to the boolean true,
and then true < 3 — the boolean true gets coerced to the number 1, so this becomes 1 < 3, which is true. The result is
true, but for the right reason only accidentally. This is a type coercion pitfall: the expression looks like it's checking a
mathematical range (1 < 2 < 3), but JS doesn't work that way. To properly check a range, write: 1 < x && x < 3."
Interview Explanation: In an interview say: "This is the counterpart to the previous question. 3 > 2 evaluates to true,
and then true > 1 — true coerces to 1, so it becomes 1 > 1, which is false. The result is false even though
mathematically 3 > 2 > 1 is a true statement. This is a great example of why understanding type coercion matters in
JavaScript. These chained comparison expressions are the kind of question interviewers use to test whether
candidates truly understand how JS evaluates expressions."
56. What is the difference between shallow equality and deep equality?
Answer: Shallow: compares references (===). Deep: recursively compares all property values.
Interview Explanation: In an interview say: "Shallow equality (===) checks if two variables point to the exact same
object in memory. It works correctly for primitives but not for objects or arrays, where two distinct objects with identical
content are not ===. Deep equality checks if every property (and nested property) has the same value, regardless of
reference. In React, PureComponent and [Link] use shallow equality to decide whether to re-render — this is
why mutating an object instead of creating a new one can cause components not to re-render. Use Lodash's _.isEqual
for deep equality in tests and utility code."
Answer: Output: 2
Interview Explanation: In an interview say: "The subtraction operator - is purely mathematical and always converts
operands to numbers. '5' becomes 5, so '5' - 3 = 2. This is different from +, which is overloaded for both addition and
string concatenation. The rule: -, *, /, and % all coerce to number. + coerces to string if either operand is a string. This
is why '5' - 3 = 2 (numeric) but '5' + 3 = '53' (string concatenation)."
Answer: Output: 15
Interview Explanation: In an interview say: "Multiplication always coerces both operands to numbers. '5' becomes 5
and '3' becomes 3, so the result is 15. This differs from + where a string would cause concatenation. A useful mental
model: think of - * / % as math-only operators that convert to numbers. Only + is 'dual-purpose' and triggers string
concatenation when a string is involved. If you want reliable numeric arithmetic, use Number() or the unary + to
explicitly convert first."
59. What is 'use strict' and why should you use it?
Answer: A directive that enables strict mode, catching silent errors and preventing unsafe features.
Interview Explanation: In an interview say: "Strict mode, enabled by adding 'use strict' at the top of a file or function,
changes several default JavaScript behaviors: it throws errors for things that were previously silent failures — like
assigning to an undeclared variable (which would otherwise create a global), deleting non-deletable properties, using
duplicate parameter names, or writing to read-only properties. It also disables some confusing features like with
statements. ES6 modules are automatically in strict mode, so if you're writing modern ESM code you get strict mode for
free. Strict mode helps catch bugs early and makes code easier to optimize."
Answer: A function that can pause execution with 'yield' and resume later, producing a sequence of values lazily.
Interview Explanation: In an interview say: "Generator functions are declared with function* and use the yield
keyword to pause and return a value. Calling a generator function returns an iterator object, not a result — you call
.next() on it to advance to the next yield. This enables lazy evaluation: values are computed only when requested. Use
cases include: infinite sequences (IDs, fibonacci), custom iterables, implementing async control flow (before
async/await), and managing complex state machines. In Redux-Saga, generators are used to orchestrate complex
async side effects."
Answer: A primitive type that creates a guaranteed unique identifier, even if two Symbols have the same description.
Interview Explanation: In an interview say: "Symbol() creates a value that is absolutely unique every time —
Symbol('id') !== Symbol('id') is always true. Symbols are often used as unique property keys on objects to avoid
naming collisions, especially in libraries and frameworks that need to store metadata on user objects without risking
overwriting user-defined properties. Well-known Symbols like [Link] and [Link] let you customize
how objects behave with language constructs like for...of loops and type coercion. Symbol-keyed properties don't show
up in [Link] or for...in loops, providing a lightweight form of privacy."
Answer: WeakMap: only object keys, no size property, allows garbage collection. Map: any key type, iterable, has
size.
Interview Explanation: In an interview say: "The key distinction is memory management. Map holds strong references
to its keys, preventing garbage collection as long as the Map exists. WeakMap holds weak references — if the only
reference to a key object is in the WeakMap, it can be garbage collected. WeakMap is ideal for storing private data or
metadata associated with DOM elements or class instances: when the element is removed from the DOM, the
associated data is automatically cleaned up without memory leaks. WeakMap is not iterable and has no .size because
the runtime may collect entries at any time. WeakSet works similarly for values."
Interview Explanation: In an interview say: "Object keys in JavaScript are always either strings or Symbols. When
you use a non-string, non-Symbol as a key — like a boolean, number, or array — JavaScript calls .toString() on it.
[Link]() returns 'true', so obj[true] and obj['true'] reference the exact same key. This can cause unexpected
collisions: obj[1] and obj['1'] are the same key; obj[[1,2]] and obj['1,2'] are the same key. This is why Maps are preferred
over objects when keys might be non-string values."
Answer: When a function's last action is a return of another function call, the current stack frame can be reused.
Interview Explanation: In an interview say: "In a tail call, the current function has nothing left to do after the recursive
call — it just returns the result. Since no local context needs to be preserved, the engine can reuse the current stack
frame instead of creating a new one, keeping stack depth constant. Without TCO, deep recursion causes a stack
overflow. JavaScript supports TCO in strict mode as per ES6 specification, though not all engines implement it. In
interviews, this is often discussed alongside recursion optimization: writing a tail-recursive version of factorial or
Fibonacci prevents stack overflow for large inputs."
Interview Explanation: In an interview say: "Classes in JavaScript are syntactic sugar over constructor functions and
prototype-based inheritance. Under the hood, a class is just a special function — its typeof returns 'function'. This
reveals an important truth: JavaScript doesn't have 'real' classes in the way Java or C++ do. The class keyword was
introduced in ES6 to make the prototype-based patterns more familiar to developers coming from class-based
languages, but the fundamental mechanics remain the same. You can verify this: class Foo{} is equivalent to a
constructor function Foo under the hood."
Answer: for...in iterates over enumerable property keys of an object. for...of iterates over iterable values.
Interview Explanation: In an interview say: "for...in is for objects — it iterates over all enumerable string keys,
including inherited ones from the prototype chain. This is why you should avoid for...in on arrays: it may iterate over
inherited properties added to [Link] by libraries. for...of is for iterables (arrays, strings, Maps, Sets,
generators) — it iterates over values, not keys. For objects, for...of doesn't work directly (objects are not iterable by
default); use [Link]() wrapped in for...of instead: for(const [key, val] of [Link](obj))."
Interview Explanation: In an interview say: "Array(3) creates a sparse array with 3 empty slots — not undefined, but
truly empty holes that .map() skips. Spreading it into a new array [...Array(3)] fills those holes with undefined, which
map() will process. The callback receives the value (_, which we ignore) and the index i, returning 0, 1, 2. This is a
common trick to generate a range of numbers. An alternative is [Link]({length:3}, (_,i)=>i), which also produces
[0,1,2] and arguably communicates intent more clearly."
Answer: Makes an object shallowly immutable — properties cannot be added, removed, or reassigned.
Interview Explanation: In an interview say: "[Link]() prevents any modifications to an object: you can't add
new properties, delete existing ones, or change their values. In strict mode, attempts to modify a frozen object throw a
TypeError; in non-strict mode they silently fail. The critical caveat: freeze is shallow — only top-level properties are
frozen. If a property value is itself an object, that nested object can still be mutated. For deep immutability you need to
recursively freeze, or use an immutability library like Immer. freeze is useful for constants and configuration objects that
should never change."
Answer: A wrapper around an object that intercepts and redefines fundamental operations like get, set, and delete.
Interview Explanation: In an interview say: "Proxy lets you define custom behavior for fundamental object operations.
You create one with new Proxy(target, handler) where handler is an object with 'trap' methods. Common traps: get
(intercept property access), set (intercept property assignment), has (intercept the 'in' operator), deleteProperty.
Real-world uses: Vue 3's reactivity system uses Proxies to track property access and trigger re-renders; validation
libraries use Proxy to enforce schemas; you can create objects with default values for missing properties. Proxy is
more powerful than [Link] because it can trap array index access, prototype operations, and function
calls too."
Interview Explanation: In an interview say: "When the + operator is used with arrays, each array is converted to a
string via .toString(), which joins elements with commas. [1,2,3].toString() = '1,2,3' and [4,5,6].toString() = '4,5,6'. String
concatenation gives '1,2,34,5,6' — notice there's no comma between 3 and 4. This is a common interview trick that
tests knowledge of type coercion. To actually concatenate arrays, use the spread operator: [...arr1, ...arr2] =
[1,2,3,4,5,6], or [Link](arr2)."
Answer: Sync blocks execution until complete. Async runs in the background and signals when done.
Interview Explanation: In an interview say: "JavaScript is single-threaded — only one operation runs at a time.
Synchronous code executes line by line; each line must complete before the next begins, which means expensive
operations (large computations, file reads) would freeze the entire application. Asynchronous code offloads
time-consuming work to the browser's Web APIs (or Node's libuv), allowing the main thread to continue. When the
async work completes, its callback/Promise is queued for execution. This non-blocking model is what makes
JavaScript suitable for I/O-heavy applications like web servers and user interfaces."
Answer: Deeply nested callbacks (pyramid of doom) caused by sequential async operations. Fix with Promises or
async/await.
Interview Explanation: In an interview say: "Callback hell occurs when you have multiple sequential async operations,
each depending on the previous one's result, leading to deeply nested callbacks that are hard to read, debug, and
maintain. Promises flatten this by letting you chain .then() calls vertically. async/await takes it further by making async
code look like synchronous code with try/catch for error handling. Additional strategies: named functions instead of
anonymous callbacks (for clarity), modularizing async operations, and using [Link] for parallel operations. Most
modern codebases use async/await as the standard."
Answer: Output: 1
Interview Explanation: In an interview say: "When booleans are used in arithmetic, they are coerced to numbers:
false becomes 0 and true becomes 1. So false + true = 0 + 1 = 1. Similarly, true + true = 2, and false + false = 0. This is
why boolean values can be used directly in arithmetic: counting truthy values in an array with .reduce((sum, b) => sum
+ b, 0) works because each true adds 1. This coercion is intentional and specified by the ECMAScript standard."
Interview Explanation: In an interview say: "With the + operator, both operands are converted to primitives first.
[].toString() returns '' (empty string), and {}.toString() returns '[object Object]'. String concatenation of '' and '[object
Object]' gives '[object Object]'. Compare this with {} + [] — if the curly brace is at the very start of a statement, JS may
parse {} as an empty block (not an object literal), and + [] = +'' = 0. This ambiguity is why these expressions can behave
differently depending on context."
Answer: A function that receives a template literal's string parts and interpolated values as separate arguments.
Interview Explanation: In an interview say: "A tagged template looks like: myTag`Hello ${name}, you are ${age} years
old`. The tag function receives: an array of the string pieces (['Hello ', ', you are ', ' years old']), and the interpolated
values (name, age) as separate arguments. This gives you full control over how the template is assembled. Real-world
uses: styled-components (css`color: ${color}`) for CSS-in-JS, graphql`query { ... }` for GraphQL query parsing, html``
for XSS-safe HTML templating, and i18n libraries for internationalisation. It's a powerful but rarely hand-implemented
feature."
Answer: typeof [] returns 'object'. [Link]([]) returns true. Always use [Link]().
Interview Explanation: In an interview say: "typeof cannot distinguish between arrays, plain objects, null, or dates —
all return 'object'. [Link]() is the correct, unambiguous way to check if a value is an array. It works correctly even
across different JavaScript realms (like iframes), where an array from another frame would fail an instanceof Array
check. In TypeScript, the type system handles this at compile time, but at runtime [Link]() remains the reliable
check."
Interview Explanation: In an interview say: "This is caused by the IEEE 754 double-precision binary floating-point
format that JavaScript (like most programming languages) uses to represent decimal numbers. 0.1 and 0.2 cannot be
represented exactly in binary, so there is a tiny rounding error that accumulates. For financial calculations or when
precision matters, the solutions are: using integer arithmetic (work in cents, not dollars), using .toFixed() for display,
using [Link] for comparisons: [Link](a - b) < [Link], or using a library like [Link] for
arbitrary-precision arithmetic."
Answer: Waits for all Promises to settle (resolve or reject) and returns all results with their status.
Interview Explanation: In an interview say: "[Link]() is the 'never fail' version of [Link](). Where
[Link] rejects immediately if any Promise rejects, allSettled waits for every Promise to complete regardless of
success or failure. It returns an array of result objects, each with a status ('fulfilled' or 'rejected') and either a value or a
reason. This is ideal when you have multiple independent operations and want all the results — even partial failures —
rather than bailing out on the first error. For example, sending multiple analytics events in parallel where a single failure
shouldn't cancel the rest."
Answer: Output: 1
Interview Explanation: In an interview say: "This demonstrates let's block scoping. The x = 2 inside the curly braces is
a completely separate variable that lives only within that block. Once the block exits, that inner x is gone. The outer x =
1 is never touched. This is a key advantage of let over var: var is function-scoped, so the inner var x would overwrite
the outer x. Block scoping with let and const prevents accidental variable shadowing bugs and makes code easier to
reason about."
Answer: Map: any key type, insertion-order iteration, O(1) for all operations, has .size. Object: string/Symbol keys, no
guaranteed order.
Interview Explanation: In an interview say: "While plain objects can be used as key-value stores, Maps are better for
several scenarios: when keys are not strings (you can use objects, functions, or DOM elements as Map keys), when
you need to iterate in insertion order reliably, when you frequently add and remove entries (Maps are optimized for
this), and when you need an accurate .size without workarounds. Objects inherit from [Link], which means
keys like 'constructor' or 'toString' can cause collisions. Maps have no prototype pollution. Use objects for static data
structures and configuration; use Maps for dynamic collections."
Answer: Flattens nested arrays by the specified depth. flat(Infinity) flattens all levels.
Interview Explanation: In an interview say: "flat() creates a new array with sub-array elements concatenated into it up
to the specified depth. flat() with no argument defaults to depth 1: [1,[2,[3]]].flat() = [1,2,[3]]. flat(Infinity) flattens all
nesting: [1,[2,[3,[4]]]].flat(Infinity) = [1,2,3,4]. There's also flatMap() which maps and then flattens one level — it's
equivalent to .map().flat(1) but more efficient. This is useful for normalizing data from APIs that return nested arrays, or
for creating flat lists from nested data structures."
Interview Explanation: In an interview say: "This is the classic JavaScript string reversal pattern. split('') converts the
string into an array of individual characters ['h','e','l','l','o']. reverse() reverses the array in place to ['o','l','l','e','h']. join('')
reassembles the characters back into a string 'olleh'. Note: this approach has a limitation — it doesn't handle Unicode
characters that are represented by surrogate pairs (like some emoji). For proper Unicode-aware reversal, you should
use the spread operator: [...str].reverse().join('')."
Answer: push/pop work at the END of the array. shift/unshift work at the BEGINNING.
Interview Explanation: In an interview say: "push(item) adds to the end; pop() removes from the end — both are O(1)
operations. unshift(item) adds to the beginning; shift() removes from the beginning — both are O(n) because all
existing elements must be re-indexed. This performance difference matters when working with large arrays: building a
queue where you add to one end and remove from the other, using push to add and shift to remove from a large array,
is O(n) per dequeue. For high-performance queues in JavaScript, a linked-list implementation or a circular buffer is
more efficient."
Answer: Output: 3
Interview Explanation: In an interview say: "[Link]() returns an array of an object's own enumerable
string-keyed property names — not inherited properties. .length on that array gives the count. The companion methods
are [Link]() (array of values) and [Link]() (array of [key, value] pairs). These are the foundation of
iterating over objects in modern JavaScript. Note: Symbol-keyed properties are not included; use
[Link]() for those. For all own properties regardless of enumerability, use
[Link]()."
Answer: Functions and undefined values are omitted from the output (silently dropped).
Interview Explanation: In an interview say: "[Link]() only supports the JSON data types: strings, numbers,
booleans, null, arrays, and plain objects. Functions, undefined, and Symbol values are not valid JSON, so: as object
values, they are omitted entirely; as array elements, they become null; as standalone values, stringify returns
undefined (not the string). Other gotchas: Date objects are converted to ISO strings (they lose their Date type), circular
references throw an error, BigInt throws a TypeError, and Map/Set/RegExp are converted to {} (empty objects). For
reliable serialization, always validate what stringify will do with your data structure."
Answer: Output: []
Interview Explanation: In an interview say: "Setting the length property of an array to a smaller value truncates the
array in place. Setting it to 0 removes all elements, making it an empty array. This is actually the fastest way to empty
an array while keeping the same array reference — useful when other parts of your code hold a reference to the same
array and you want all of them to see an empty array. Alternative ways to empty an array: [Link](0) (modifies in
place), arr = [] (creates a new array, other references to the old array are unaffected)."
Answer: innerHTML parses and renders HTML tags. textContent treats everything as plain text.
Interview Explanation: In an interview say: "innerHTML sets or gets the HTML markup inside an element — tags are
parsed and rendered as HTML. textContent sets or gets the plain text content — any HTML tags are treated as literal
text. The critical security implication: setting innerHTML with user-provided data exposes your app to Cross-Site
Scripting (XSS) attacks. A malicious user could inject steal(). textContent is safe because it never interprets HTML. The
rule: always use textContent when setting text from user input. Only use innerHTML when you trust the content and
explicitly need to render HTML markup. For safe HTML rendering, use a sanitization library like DOMPurify."
Answer: A function that: (1) returns the same output for the same input, and (2) has no side effects.
Interview Explanation: In an interview say: "A pure function is deterministic and self-contained. Given the same
arguments, it always returns the same result — it doesn't read from or write to anything outside its own scope. It
doesn't mutate its arguments, doesn't call APIs, doesn't write to the DOM, and doesn't use random numbers or
[Link](). The benefits: pure functions are predictable (easy to test), cacheable (safe to memoize), and
parallelizable. React strongly encourages pure render functions and reducers — a Redux reducer must be pure
because the same action + state always produces the same new state. In interviews, recognizing and writing pure
functions demonstrates solid functional programming knowledge."
Interview Explanation: In an interview say: "The unary + operator is the shortest way to coerce a value to a number.
+'3' = 3, +'3.14' = 3.14, +true = 1, +false = 0, +null = 0, +'' = 0, +undefined = NaN, +'abc' = NaN, +[] = 0, +[3] = 3. It's
commonly used in code golf and quick type conversions, but Number() is more explicit and readable in production
code. Both have the same behavior."
Interview Explanation: In an interview say: "'key' in obj returns true if the property exists anywhere in the object's
prototype chain — including inherited properties. This is different from hasOwnProperty(), which only checks the
object's own properties: 'toString' in {} is true (inherited), but {}.hasOwnProperty('toString') is false. For arrays, the in
operator checks for index existence: 0 in [1,2,3] is true. A practical use: safely checking if an object has a method
before calling it, especially when working with objects from external sources. In modern JS, [Link](obj, key) is
the recommended replacement for hasOwnProperty()."
Answer: Output: 1
Interview Explanation: In an interview say: "null is coerced to 0 in numeric context. So null + 1 = 0 + 1 = 1. This is in
contrast to undefined, which coerces to NaN — so undefined + 1 = NaN. A useful memory aid: null is a deliberately set
'empty' value and JavaScript treats it as 0 in arithmetic; undefined is truly unknown/unset and produces NaN. However,
null + '1' = 'null1' because the + operator with a string triggers concatenation and null is converted to the string 'null'."
Interview Explanation: In an interview say: "undefined converts to NaN when used in numeric operations. NaN + 1 =
NaN. This is different from null (which converts to 0). The reasoning: null means 'explicitly no value' and maps cleanly
to zero; undefined means 'unset/unknown', and any arithmetic involving an unknown quantity is meaningless — hence
NaN. This distinction matters in practice: if a function parameter is missing, it's undefined, and accidentally using it in
math will produce NaN silently — always validate inputs!"
Answer: Copies own enumerable properties from one or more source objects into a target object.
Interview Explanation: In an interview say: "[Link](target, ...sources) merges sources into target and returns
target — the target is mutated. It's commonly used for shallow cloning: [Link]({}, original) copies all own
properties into a new object. It's also used for merging configuration objects: [Link]({}, defaults, userOptions) —
later sources overwrite earlier ones for duplicate keys. Key limitations: it's a shallow copy (nested objects share the
same reference), it doesn't copy Symbol-keyed properties that aren't enumerable, and it doesn't copy getters (it
evaluates them and copies the resulting value). The spread syntax {...source} is usually preferred now as it's more
concise and reads more clearly."
Interview Explanation: In an interview say: "Strings support bracket notation for character access, just like arrays.
Strings are zero-indexed, so 'abc'[0] = 'a', 'abc'[1] = 'b', 'abc'[2] = 'c'. However, unlike arrays, you cannot assign to string
indices — 'abc'[0] = 'z' silently fails because strings are immutable primitives in JavaScript. To modify a string you must
create a new string. The charAt() method does the same thing but handles out-of-bounds differently: 'abc'[5] returns
undefined, while 'abc'.charAt(5) returns an empty string."
95. What is the difference between [Link]() and the spread operator for iterables?
Answer: Both convert iterables to arrays. [Link]() additionally accepts a mapping function as a second argument.
Interview Explanation: In an interview say: "[Link](iterable) and [...iterable] both create an array from any iterable
(strings, Maps, Sets, NodeLists, etc.). The key advantage of [Link] is the optional second argument — a map
function: [Link]({length:5}, (_, i) => i*2) creates [0,2,4,6,8] in one step. This is especially useful for creating ranges.
[Link] also handles array-like objects (objects with .length and numeric keys, like arguments) while spread
requires true iterables. For sparse arrays, [Link] creates dense arrays (holes become undefined), while spread
preserves sparseness."
Interview Explanation: In an interview say: "Both '' (empty string) and 0 are falsy values in JavaScript. Boolean('') =
false and Boolean(0) = false. Comparing false === false gives true. This question tests whether you know that both
empty string and zero are falsy, and that Boolean() explicitly converts to the boolean primitive. In interviews, a natural
follow-up is: what about Boolean('0')? That returns true — '0' is a non-empty string and therefore truthy, which
surprises many developers used to other languages where '0' is considered falsy."
Interview Explanation: In an interview say: "** is the exponentiation operator introduced in ES2016 (ES7). 2 ** 10 = 2
to the power of 10 = 1024. It's equivalent to [Link](2, 10). One interesting behavior: the right-associativity of **: 2 **
3 ** 2 evaluates as 2 ** (3 ** 2) = 2 ** 9 = 512, not (2 ** 3) ** 2 = 64. This right-to-left evaluation is how exponentiation
works in mathematics."
Answer: false, 0, -0, 0n, '' (empty string), null, undefined, NaN — exactly 8 values.
Interview Explanation: In an interview say: "Knowing the complete list of falsy values is essential. There are exactly 8:
false, 0, -0, 0n (BigInt zero), '' or '' (empty string), null, undefined, and NaN. Every other value is truthy — including
some that surprise developers: '0' (non-empty string) is truthy, [] (empty array) is truthy, {} (empty object) is truthy, and
-1 is truthy. This matters for conditional checks: if([Link]) is a safe check because 0 is falsy. But if(arr) is also true
for an empty array because [] is truthy. Knowing this list prevents bugs and is a common interview filter question."
Interview Explanation: In an interview say: "[Link]() returns a boolean indicating whether the given
substring exists anywhere in the string. It's case-sensitive: 'Hello'.includes('hello') returns false. You can also pass a
second argument for the starting position: 'hello'.includes('ell', 2) starts searching from index 2 and returns false.
includes() was introduced in ES6 as a cleaner alternative to 'hello'.indexOf('ell') !== -1. For pattern matching with
regular expressions, use .match() or .test() on a RegExp instead."
Answer: localStorage persists indefinitely. sessionStorage is cleared when the browser tab is closed.
Interview Explanation: In an interview say: "Both are part of the Web Storage API and store key-value pairs as strings
with a ~5MB limit per origin. The critical difference is lifetime: localStorage data persists across browser sessions until
explicitly cleared by code ([Link]) or the user. sessionStorage data exists only for the duration of the
page session — closing the tab destroys it, and it's not shared between tabs even of the same origin. Both are
synchronous and can block the main thread for large reads/writes. For large or sensitive data, prefer IndexedDB
(async, larger storage) or cookies for server communication. Never store sensitive data like tokens in localStorage due
to XSS vulnerability risks."