What is the output?
typeof null === 'object' is a long-standing bug in JavaScript that was never fixed for backwards-compatibility reasons. null is its own primitive type — not an object.
Master 180 concepts with detailed explanations.
typeof null === 'object' is a long-standing bug in JavaScript that was never fixed for backwards-compatibility reasons. null is its own primitive type — not an object.
The unary plus (+) coerces its operand to a number. true converts to 1, so +true is 1. typeof 1 is 'number'.
Non-empty strings are truthy. ! negates that truthy value to false. typeof operates on the original string literal 'Abhijeet', not the result of !, so it returns 'string'.
0.1 + 0.2 produces 0.30000000000000004 due to IEEE 754 floating-point representation. It is not strictly equal to 0.3. Use Math.abs(a - b) < Number.EPSILON for safe float comparisons.
var declarations are hoisted to the top of their function scope and initialized as undefined, but the assignment stays in place. So console.log(x) sees the hoisted x (undefined) before x = 2 runs.
var is function-scoped, not block-scoped. Redeclaring greeter inside the if block overwrites the same variable in the outer scope. The final value is 'say Hello instead'.
let is block-scoped. hello exists only inside the if block, so the first console.log works fine. Outside the block, hello is not in scope — accessing it throws a ReferenceError.
var is function-scoped. hello is declared inside newFunction and is completely invisible outside it. Accessing hello in the global scope throws a ReferenceError.
var name is hoisted and initialized as undefined, so the first log prints undefined. let price is in the Temporal Dead Zone (TDZ) at the point of the second log, so accessing it throws a ReferenceError.
var is function-scoped, so all three callbacks close over the same i variable. By the time the event loop runs them, the loop has finished and i is 3. Replacing var with let gives each iteration its own i, printing 0, 1, 2.
var is not block-scoped, so j leaks out of the for loop. The loop increments j until j < 10 fails, leaving j at 10. It is still accessible after the loop ends.
A closure is formed when an inner function retains access to the lexical scope of its outer function, even after the outer function has finished executing. This enables data encapsulation and function factories.
count is a private variable captured by the closure. The returned object only exposes increment() and getCount() — there is no count property on it. counter.count looks up a non-existent property, returning undefined.
Each call returns a new function that accumulates the sum via closure. 1 + 2 + 3 + 4 = 10. The final empty call () receives no argument, so b is undefined (falsy) and the accumulated value 10 is returned.
Objects are assigned by reference in JavaScript. copy and obj point to the same object in memory, so mutating copy.a also changes obj.a. The output is 99.
d = c makes both variables reference the same object. Updating c.name to 'peter' is immediately visible through d because they share the same memory reference.
When two spread objects share a key, the last one wins. data1 is spread after data, so its name 'Joe' overwrites data's 'John'. The final object is { name: 'Joe', city: 'Mumbai' }.
Without the spread (...), data is placed as a whole object under the literal key 'data'. Only data1 is spread into individual properties. Result: { data: { name: 'Abhijeet' }, city: 'Mumbai' }.
Object.assign(target, source) copies source properties into target and returns the same target object. person2 overwrites person1's name to 'Jeet'. Since person === person1, both log 'Jeet'.
Plain object keys are always coerced to strings. Both b and c stringify to '[object Object]', so a[c] = 456 silently overwrites a[b] = 123. Reading a[b] returns 456.
bird[data] evaluates the variable data to 'size', returning 'small'. bird['size'] and bird.size also access the same key. bird.data looks for a literal key named 'data' — which doesn't exist — returning undefined.
Without brackets, 'property' is used as the literal key name. With [property], JavaScript evaluates the variable and uses its value 'firstName' as the key. This is called a computed property name.
Synchronous code runs first: A, then D. Microtasks (Promise .then) always run before macrotasks (setTimeout). So C from the Promise runs before B from setTimeout. Order: A → D → C → B.
In first(), .then() is async so 'First' logs before its 'Success'. In second(), await pauses execution, so 'Second' comes after the awaited 'Success'. Microtask queue order: First → Success → Success → Second.
console.log(isLoading) is synchronous and executes first while isLoading is still true. .then() and .finally() are microtasks — they run after all synchronous code. Order: true → One → Two.
Promise.all() is fail-fast — it rejects immediately as soon as any one input promise rejects. Other pending promises continue running but their results are discarded. Use Promise.allSettled() when you need every outcome.
catch() handles the rejection and logs error.message ('Failed!!'). Since catch() has no explicit return, it implicitly returns undefined. That undefined becomes the resolved value passed to the chained then().
Synchronous: 1, 3, 5, 7. The first Promise resolves to 4 but has no .then(), so 4 is never logged. The second Promise's .then(console.log) schedules 6 as a microtask. setTimeout's 2 is a macrotask. Final order: 1 → 3 → 5 → 7 → 6 → 2.
The delete operator only removes object properties — it has no effect on local variables or function parameters. delete a does nothing here; the parameter a keeps its value of 5, which is returned and logged.
|| returns the first truthy value it encounters. false is falsy, but {} (an empty object) is truthy. Evaluation short-circuits at {} and returns it — 20 and null are never evaluated.
When all operands are falsy, || returns the last one. null, false, and '' are all falsy, so the result is '' — the final value in the chain.
Empty arrays are truthy in JavaScript. || returns the first truthy value, which is []. Evaluation never reaches 0 or true.
?? (nullish coalescing) only falls back when the left side is null or undefined. Unlike ||, it does not treat 0, '', or false as nullish. null ?? 'default' → 'default'. 0 ?? 'default' → 0.
Object.create(person) creates an empty object whose [[Prototype]] is set to person. jayesh has no own displayName method, so JavaScript walks the prototype chain, finds it on person, and calls it with jayesh as this. Since jayesh has no own name, it also walks the chain and finds 'Sonu' on person.
The inner let name shadows the outer one. let is hoisted but placed in the Temporal Dead Zone (TDZ) until its declaration is reached. The console.log hits the TDZ of the inner name — throwing a ReferenceError instead of falling back to the outer scope.
let name; declares the variable without a value. Unlike the TDZ case, the declaration exists before the log — so no error is thrown. An uninitialized let holds undefined until assigned.
Array destructuring assigns by position. The leading comma skips index 0 ('John'). m is bound to index 1 ('Joe'). The rest of the array is ignored.
{ name: fullName } destructures the name property and binds its value to a local variable called fullName. age is destructured normally. Output: 'Alice' 30.
A string is a primitive — it is not callable. Invoking it with () throws a TypeError: name is not a function.
reduce(callback, initialValue) calls callback(accumulator, currentValue) for every element and returns the final accumulated value. It can produce a number, string, object, or any other type from an array.
map() always returns a new array and never modifies the original. push(), splice(), and sort() all mutate the array in place, which is a common source of bugs when working with shared state.
Without a comparator, sort() converts elements to strings and sorts lexicographically. '10' comes before '2' because '1' < '2'. Use nums.sort((a, b) => a - b) for numeric ordering.
The event loop continuously checks if the call stack is empty. When it is, it dequeues the next callback — first from the microtask queue (Promises), then from the macrotask queue (setTimeout, setInterval).
The spread operator (...arr) expands an iterable into individual elements. Common uses: cloning ([...arr]), merging ([...a, ...b]), and spreading arguments into function calls (Math.max(...nums)).
Spread creates a shallow copy. Top-level primitives are copied by value, but nested objects are still shared by reference. Both a and b point to the same nested object, so mutating b.nested.y also changes a.nested.y.
When getVal is assigned to fn and called as a plain function, it loses its obj context. In non-strict mode, this defaults to the global object, which has no val property — so this.val is undefined.
Arrow functions capture 'this' from their enclosing scope when they are defined and never change it. Regular functions get 'this' dynamically based on how they are called — different call sites can give different values.
bind(thisArg) returns a new function where 'this' is permanently bound to thisArg. Unlike call() and apply(), bind() does not invoke the function immediately — it hands back a reusable bound version.
Bubbling is the default propagation behavior: an event starts at the target and travels upward through parent elements to the root. Event capturing is the opposite direction — root to target — and must be explicitly enabled.
Attributes (written in HTML) are static — they represent the initial value and don't change on their own. Properties (on the DOM node) are live — they reflect the current state. An input's value attribute is its starting value; value property tracks what the user has typed.
Function declarations are fully hoisted — you can call them before they appear in the source. var is hoisted but only initialized to undefined (not its assigned value). let, const, and class are hoisted but stay in the Temporal Dead Zone until their declaration line.
greet is a function declaration — fully hoisted, so calling it before the line it appears on works fine and returns 'Hello'. greet2 is a const holding a function expression. The const binding exists (it was hoisted) but greet2 is only called on the last line, after the assignment, so it resolves correctly and returns 'Hi'.
== (abstract equality) converts operands to the same type before comparing — '5' == 5 is true. === (strict equality) requires both value and type to match — '5' === 5 is false. Prefer === to avoid surprising coercion bugs.
[] == false triggers abstract equality coercion: [] → '' (via toString) → 0 (via ToNumber), and false → 0. 0 == 0 is true. [] === false is false immediately because they are different types (object vs boolean). No coercion happens with ===.
const prevents rebinding the variable (arr = something else would throw), but it does not freeze the object or array itself. push() mutates the array in place — the binding stays the same reference, so no error is thrown.
Object.freeze() prevents adding, removing, or changing properties on an object (shallow freeze). const only prevents reassigning the variable. Object.seal() prevents adding/removing but still allows value changes. Object.assign() creates a copy — it does nothing for immutability.
Calling a generator function does not execute its body. It returns an iterator (Generator object) with a .next() method. Each .next() call resumes execution until the next yield or return, producing { value, done } objects.
Each .next() runs until the next yield or return. yield sets done: false; return sets done: true. So the first two .next() calls yield 1 and 2 with done: false. The third hits return 3 — value is 3, done is true.
Symbol() creates a guaranteed-unique primitive. Two Symbol() calls with the same description are never equal: Symbol('x') !== Symbol('x'). Symbols are commonly used as non-string object keys to avoid naming conflicts with other libraries or framework internals.
When a property isn't found on an object, the engine walks up the [[Prototype]] chain — checking each prototype object in turn. If found anywhere in the chain, that value is returned. If the chain ends at null (Object.prototype's prototype) without finding it, undefined is returned.
dog.speak() works because speak is found on Animal.prototype via the prototype chain. However, speak is not an own property of dog — it lives on the prototype. hasOwnProperty returns false for inherited properties.
ES6 classes are syntactic sugar. Under the hood, class Foo {} creates a constructor function. Methods defined in the class body are added to Foo.prototype. The prototype chain works identically to manually wired constructor functions.
super(color) calls Shape's constructor, setting this.color. getColor() is inherited from Shape.prototype, so c.getColor() returns 'red'. instanceof checks the prototype chain — Circle.prototype inherits from Shape.prototype, so c instanceof Shape is true.
throw immediately exits riskyOp and jumps to catch — 'after' never logs. catch receives the TypeError, so e instanceof TypeError is true and e.message is 'Bad input'. finally always runs regardless of whether an error occurred, logging 'cleanup'.
finally runs unconditionally — after try or catch, whether the block exits via normal completion, return, throw, or break. The return value is held until finally completes. If finally itself contains a return, it overrides the try's return value.
Named exports (export const foo) can have many per file and must be imported with matching names: import { foo } from './mod'. A default export (export default) is one per file and can be imported with any name: import anything from './mod'.
ES module import is static: bundlers and engines can analyze dependencies at parse time, enabling tree-shaking and circular dependency detection. CommonJS require() is a runtime function call — you can use it conditionally, pass dynamic paths, and it runs when the line executes.
An object is iterable if it has a [Symbol.iterator]() method that returns an iterator (an object with .next() returning { value, done }). Arrays, strings, Maps, Sets, and generator objects are iterable by default. You can make any custom object iterable by implementing this protocol.
Strings are iterable — they implement [Symbol.iterator] that yields each Unicode character. Spreading a string into an array produces an array of individual characters. 'hello' has 5 characters; chars[0] is 'h'.
Plain object keys are always coerced to strings (or Symbols). Map keys can be any value: objects, functions, numbers, NaN. Map also guarantees insertion-order iteration and provides a native .size property. Use Map when keys aren't strings or order matters.
Set stores only unique values. Passing [1, 2, 2, 3, 3, 3] results in {1, 2, 3} — size is 3. Adding 2 again has no effect because 2 already exists. Size remains 3.
The 'weak' in WeakMap means the key is held by a weak reference — if no other reference to the key object exists, the GC can collect it and the entry disappears automatically. Primitives like numbers and strings live forever (they're value types), so allowing them as keys would defeat the memory-management purpose entirely.
The second argument to reduce() is the initial accumulator value (10). The callback adds each element: 10 + 1 = 11, 11 + 2 = 13, 13 + 3 = 16.
flat() with no argument defaults to depth 1. It flattens one level: the outer arrays are unwrapped, but the nested [4, 5] survives. Use flat(Infinity) to recursively flatten all nesting levels.
find() short-circuits and returns the first element for which the callback returns true, or undefined if none match. filter() iterates the entire array and returns a new array containing every matching element. Neither mutates the original.
An async function always returns a Promise. If the function returns a non-Promise value (e.g., 42), that value is automatically wrapped: Promise.resolve(42). If it throws, the returned Promise rejects with that error.
async functions always return a Promise. The returned object is the resolved value, accessible in .then(). u is { id: 1, name: 'Alice' }, so u.name is 'Alice'.
await unwraps a rejected Promise and throws the rejection reason as an error inside the async function. try/catch catches it. e.message is 'oops', so the output is 'caught: oops'.
Both call() and apply() invoke the function immediately with a specified 'this'. The only difference is argument passing: fn.call(ctx, a, b) vs fn.apply(ctx, [a, b]). bind() is the version that does not invoke immediately — it returns a new bound function.
call(user, 'Hello', '!') sets this to user and passes arguments individually. apply(user, ['Hi', '?']) does the same but receives arguments as an array. In both cases, this.name resolves to 'Abhijeet'.
An IIFE (Immediately Invoked Function Expression) is a function defined and called in one expression: (function() { ... })(). Variables declared inside are scoped to that function and don't leak to the global scope. Before ES modules, IIFEs were the primary tool for avoiding global namespace pollution.
Currying transforms f(a, b, c) into f(a)(b)(c). Each call returns a new function waiting for the next argument. It enables partial application — fixing some arguments ahead of time to create specialized functions — and is heavily used in functional programming patterns.
multiply(2) returns b => 2 * b, assigned to double. double(5) returns 2 * 5 = 10. multiply(3)(4) returns 3 * 4 = 12.
Memoization stores the result of a pure function call keyed by its arguments. On subsequent calls with identical arguments, the cached result is returned without re-executing the function body. It trades memory for speed and is most beneficial for expensive, frequently called pure functions.
A pure function has two requirements: (1) same input always produces same output, and (2) no side effects. Option A has a side effect (console.log). Option B's output changes over time. Option D mutates the input array. Only option C is deterministic and side-effect-free.
user?.profile returns null. null?.name short-circuits immediately and returns undefined (not a TypeError). user?.address returns undefined (no address property), so undefined?.city is also undefined. undefined ?? 'Unknown' falls back to 'Unknown' because undefined is nullish.
Without ?., accessing a.b.c throws a TypeError if a.b is null or undefined. Optional chaining short-circuits the entire expression to undefined when it encounters a nullish value, eliminating the need for manual if (a && a.b && a.b.c) guards.
Template literals are still strings — typeof returns 'string'. The ${name} interpolation is resolved at evaluation time, producing 'Hello, World!'. Template literals only become tagged templates when preceded by a tag function.
JavaScript's falsy values are: false, 0, -0, 0n (BigInt zero), '' (empty string), null, undefined, and NaN — eight in total. 0 is one of them. 'false' (non-empty string), [] (object), and {} (object) are all truthy — including empty arrays and objects.
'' is an empty string (falsy) → false. 0 is falsy → false. [] is an empty array — but all objects (including arrays) are truthy → true. {} is an empty object, also truthy → true.
1 + '2': + with a string triggers string concatenation → '12'. '3' - 1: - has no string behavior, so '3' is coerced to the number 3 → 2. '6' / '2': / coerces both strings to numbers → 3. Arithmetic operators other than + always convert to numbers.
Modern JavaScript engines (V8, SpiderMonkey) use mark-and-sweep. Starting from roots (global, stack), the GC marks every reachable object. Anything unmarked is considered garbage and freed. This correctly handles circular references, which pure reference counting cannot.
JSON.stringify() silently omits properties whose values are undefined, functions, or Symbols. null is a valid JSON value and is kept. So only a and d make it into the output string.
Proxy lets you define custom behavior for fundamental operations on an object. You provide handler traps (get, set, has, deleteProperty, etc.) that run when those operations are performed. Vue 3 uses Proxy for its reactivity system; it's also used for validation, logging, and virtual DOM patterns.
In a tail call, the recursive call is the very last thing the function does — so the current stack frame is no longer needed. TCO reuses the same frame instead of pushing a new one, keeping stack depth constant. ES6 mandates TCO in strict mode, though most engines only partially implement it.
Every function carries a reference to the environment it was defined in. When resolving a variable, JavaScript checks the current scope first, then follows the outer environment reference up the chain until it finds the variable or reaches the global scope. This chain is fixed at definition time (lexical scope), not call time.
Every time code runs, the engine creates an execution context: a structure containing the variable environment (where declared variables live), the scope chain (outer environment references), and the value of 'this'. There is a global execution context and one created for each function call. These are pushed/popped on the call stack.
Debounce: resets a timer each call and fires only after calls stop for the specified delay — good for search-as-you-type (fire after the user pauses). Throttle: fires at most once per interval no matter how fast calls come in — good for scroll/resize handlers to limit rate.
writable: false means silent assignments are ignored in non-strict mode (a TypeError in strict mode). The value stays 42. enumerable: false hides the property from Object.keys(), JSON.stringify(), and for...in loops. It still exists and is readable; it's just hidden from enumeration.
Setting arr[10] = 99 makes the array sparse — indices 3 through 9 are empty slots. length becomes 11 (highest index + 1). Accessing arr[5] returns undefined because the slot is empty, not because a value of undefined was stored there.
NaN is the only JavaScript value that is not equal to itself. NaN === NaN is false. Number.isNaN() checks strictly — it returns true only for the actual NaN value, not for strings that look like 'NaN'. The global isNaN() would coerce 'NaN' to NaN and return true, but Number.isNaN() does not coerce.
Each call to outer() creates a new closure with its own independent x variable. fn1 and fn2 have separate x values, both starting at 10. fn1() increments its x to 11, then 12. fn2() starts fresh from 10, increments to 11 — independent of fn1's state.
double is a regular method so 'this' is obj when called as obj.double(). It returns an arrow function, which captures 'this' from double's lexical scope — still obj. So the arrow function returns obj.value * 2 = 1 * 2 = 2.
filter(n => n % 2 === 0) keeps even numbers: [2, 4]. map(n => n * 3) multiplies each by 3: [6, 12]. The odd numbers 1, 3, 5 are discarded by filter and never reach map.
new Number(3) creates a Number object wrapper, not a primitive. == coerces it: the object is unwrapped to its primitive value 3, so 3 == 3 is true. === checks type too — a is a number primitive, b is an object — different types, so false.
Rest syntax in destructuring collects remaining elements into an array. a gets 1, b gets 2, and rest gets all remaining elements [3, 4, 5]. The rest element must always be last in the destructuring pattern.
Rest parameters collect all passed arguments into a real Array. args is [1, 2, 3], so args.length is 3 and Array.isArray(args) is true. This contrasts with the legacy arguments object, which is array-like but not a real Array.
Private class fields (prefixed with #) are not exposed as regular properties. c.value returns 2 through the getter, which has internal access to #count. c.count (no #) looks for a regular property named 'count' on the instance — it doesn't exist, so it returns undefined. The private field #count is completely inaccessible from outside the class body.
Promise.allSettled() waits for every promise to settle (unlike Promise.all() which rejects fast). Each result object has a status of 'fulfilled' or 'rejected'. The rejection of promise3 doesn't abort the operation — all three results are collected.
The Proxy's get trap intercepts all property access. For proxy.x, key 'x' exists in target so target[key] = 1 is returned. For proxy.y, 'y' is not in target so the trap returns 'not found' instead of undefined.
Each .then() returns a new Promise with the handler's return value. 1 + 1 = 2, then 2 * 2 = 4. The final .then logs 4.
First .then logs 'start' and returns 'middle'. Second .then logs 'middle' but has no return — implicit undefined. Third .then receives and logs undefined.
.catch() handles the rejection, logs 'caught error', and returns 'recovered'. A .catch() that returns a value produces a fulfilled Promise, so the chained .then() receives 'recovered' and logs it.
await unwraps the resolved value 42. The async function returns a Promise that resolves with 42. .then(console.log) prints 42.
'1' logs synchronously. main() is called — '2' logs synchronously inside it. await null suspends main() and schedules the rest as a microtask. Control returns to the caller, '4' logs. Then the microtask runs and '3' logs.
An async function that throws returns a rejected Promise. .catch() handles the rejection and logs e.message ('boom'). Since .catch() returns undefined (no explicit return), the chained .then() runs and logs 'done'.
Promise.all() waits for all promises to fulfill and resolves with an array of their results in the same order they were passed. console.log([1, 2, 3]) prints the array.
run() is called and immediately hits await delay(0). setTimeout(0) is a macrotask — it yields control. 'B' logs synchronously. After all synchronous code and microtasks finish, the setTimeout callback fires, resolving the delay, and 'A' logs.
await one() resolves to 1, await two() resolves to 2. a + b = 3. console.log prints 3.
Promise.race() resolves or rejects as soon as the first promise settles. The 10ms promise resolves first with 'fast', so that is the resolved value.
The Promise executor runs synchronously. x is set to 1, then resolve(1) is called — the resolved value is captured as 1. x is then set to 2. When the .then() microtask runs, val is 1 (the captured value) but x is now 2.
Promise.all() rejects immediately when any promise rejects. 'b' is the rejection reason. The await throws, the async function rejects, and the outer .catch() logs 'caught: b'.
'start' logs synchronously. First await suspends — 'sync' logs. Microtask resumes: 'middle' logs. Second await suspends briefly again. Microtask resumes: 'end' logs.
A Promise can only settle once. The first resolve(1) transitions it to fulfilled with value 1. The second resolve(2) is silently ignored — the state is already locked. .then() receives 1.
map passes (element, index) to the callback. 1 * 0 = 0, 2 * 1 = 2, 3 * 2 = 6. Result: [0, 2, 6].
flat(Infinity) recursively flattens all nesting levels. The result is a fully flat array [1, 2, 3, 4].
flatMap maps each word to its character array then flattens one level. 'hello' → ['h','e','l','l','o'] (5 chars) + 'world' → ['w','o','r','l','d'] (5 chars) = 10 elements.
new Set() removes duplicates while preserving insertion order. 1 appears at index 1 first, so the second 1 is dropped. Spreading back to an array gives [3, 1, 4, 5].
slice(start, end) returns elements from index start up to but NOT including index end. slice(1, 3) returns indices 1 and 2: [2, 3].
splice(1, 1, 9, 10) starts at index 1, removes 1 element (the 2), and inserts 9 and 10 in its place. Original [1, 2, 3] becomes [1, 9, 10, 3].
filter(Boolean) removes falsy values. All elements — 1, 2, 3, 4 — are truthy numbers, so none are removed. The result is the same array contents [1, 2, 3, 4].
filter(Boolean) keeps only truthy values. 0, '', null, undefined, and false are all falsy and get removed. Only 1 and 2 survive.
'b' is at index 1. indexOf returns -1 when the element is not found — it never returns undefined or null.
every() returns true only if ALL elements pass the test. 1, 2, 3 are all > 0 → true. some() returns true if AT LEAST ONE element passes. 3 > 2 → true.
Starting with [], reduce concatenates each row array into the accumulator. [] + [1,2] = [1,2], then [1,2] + [3,4] = [1,2,3,4].
Object.keys() returns an array of the object's own enumerable property names. Object.values() returns an array of the corresponding values.
Object.entries() returns an array of [key, value] pairs for every own enumerable property.
Object.fromEntries() is the inverse of Object.entries() — it converts an iterable of [key, value] pairs into an object.
Object.freeze() prevents all mutations to the object. In non-strict mode, attempts to modify or add properties are silently ignored. obj stays { a: 1 }.
Destructuring defaults apply when the value is undefined. b is explicitly undefined in the source, so the default 10 is used. c is missing entirely, so 20 is used. a gets 1.
slice(0, 4) gives characters at indices 0–3: 'Java'. slice(-6) counts 6 from the end of 'JavaScript' (length 10), starting at index 4: 'Script'.
trim() removes leading and trailing whitespace: 'hello world'. split(' ') splits on the single space between the words: ['hello', 'world']. Length is 2.
padStart pads to total length 5 by adding fill characters at the START: '00abc'. padEnd pads at the END: 'abc--'.
'ell' is a substring of 'hello' → true. 'hello' starts with 'he' → true. 'hello' ends with 'lo' → true.
repeat(3) produces 'hahaha'. replace() replaces only the first match: 'aXc'. replaceAll() replaces every match: both 'b's become 'Y' → 'aaYYcc'.
Math.max(1,3,2) is 3. Math.max() with no arguments returns -Infinity (identity element for max). Math.min() with no arguments returns Infinity (identity element for min).
parseInt('10', 2) parses '10' in base 2 → 2. parseInt('10', 16) parses '10' in hex → 16. parseInt('10.9') parses the integer part only → 10.
typeof NaN is 'number'. The global isNaN() coerces its argument: 'hello' becomes NaN → returns true. Number.isNaN() does NOT coerce — 'hello' is a string, not NaN → returns false.
Both arrow functions close over the same count variable. inc() twice: 0→1→2. dec() once: 2→1. Final value is 1.
makeAdder(5) returns a closure that adds 5 to its argument. add5(3) = 5+3 = 8. add5(7) = 5+7 = 12.
var a = b = 3 is parsed as b = 3 (implicit global) then var a = b. Inside the IIFE, a is local (var) and b becomes a global. Outside: typeof a is 'undefined' (a doesn't exist in this scope). typeof b is 'number' (b leaked globally).
The loop uses let, so each iteration creates a new binding for i. Each closure captures its own unique i: 0, 1, 2 respectively. The outer let i = 0 is shadowed and irrelevant.
Destructuring extracts greet as a standalone function. When called without a receiver, this defaults to the global object (or undefined in strict mode), which has no name property. this.name is undefined.
new Person creates an object whose [[Prototype]] is Person.prototype, so instanceof returns true. constructor is found on Person.prototype and points back to Person, so === comparison is true.
super.greet() calls the Base class's greet method, returning 'Hello from Base'. The Child method appends ' and Child', producing 'Hello from Base and Child'.
JSON.parse(JSON.stringify(obj)) creates a true deep clone — nested objects are also copied. Mutating clone.b.c does not affect obj.b.c, which stays 2.
Arrays are objects. Both == and === compare object references, not contents. a and b are separate objects in memory, so both comparisons return false.
toString() is inherited from Object.prototype and returns '[object Object]'. It is NOT an own property of obj, so hasOwnProperty('toString') returns false.
** is the exponentiation operator: 2**10 = 1024. 5 % 3 = 2. In JavaScript, % preserves the sign of the dividend: -7 % 3 = -1.
for...in iterates over the enumerable property NAMES (keys) of an object, not its values. It logs 'a' then 'b'.
Automatic Semicolon Insertion (ASI) adds a semicolon after return on its own line. The function returns undefined. The object literal on the next line is dead code.
x = 5. 5 > 3 is true, so evaluate the nested ternary: 5 > 4 is true → 'big'. Result is 'big'.
Array destructuring swaps a and b without a temporary variable. The right side [b, a] = [2, 1] is evaluated first, then a gets 2 and b gets 1.
Arrow functions do not have their own 'this' — they inherit it from the enclosing lexical scope. At the top level (or module), this is undefined (strict) or the global object (which has no val property). this?.val short-circuits to undefined.
Default parameters apply only when the argument is undefined (or not passed). greet() and greet(undefined) both trigger the default 'stranger'. greet(null) passes null explicitly — null is not undefined, so it is used as-is.
Array.at() supports negative indexing. at(0) = first element = 1. at(-1) = last = 5. at(-2) = second from end = 4.
Setting an existing key overwrites its value — it does not add a new entry. After three set calls with two distinct keys ('a' and 'b'), size is 2. map.get('a') returns the latest value: 3.
Spreading a string gives individual characters: ['h','e','l','l','o']. reverse() mutates to ['o','l','l','e','h']. join('') reassembles to 'olleh'.
Classes are syntactic sugar over constructor functions. typeof reports both a class and a function expression as 'function'. There is no 'class' type in JavaScript's type system.
Spreading nums creates a shallow copy. sort() mutates only the copy. sorted is sorted ascending; nums remains the original [5, 3, 8, 1].
The in operator checks own AND inherited properties. 'a' is an own property → true. 'toString' is inherited from Object.prototype → also true. Use hasOwnProperty to check only own properties.
JavaScript uses lexical scoping. show() was defined in the global scope where x = 10. Even though it is called from inside change() (where let x = 20), show() always sees its own lexical scope's x, which is the global 10.
Object rest in destructuring collects all remaining own enumerable properties not explicitly destructured. a gets 1; rest gets { b: 2 }.
new Function() creates a function from a string of source code at runtime. add(2, 3) returns 5. typeof any function is 'function'.
Comparisons evaluate left to right. 1 < 2 = true, then true < 3 = 1 < 3 = true. 3 > 2 = true, then true > 1 = 1 > 1 = false.
obj.__proto__ is Object.prototype — the prototype shared by all plain objects. Adding hello to it makes it available on every plain object, including obj2. This is prototype pollution — a common security concern.
Array.from({ length: 3 }, mapFn) creates an array of 3 elements. The map function receives (value, index) — value is undefined since the object has no numeric keys, index is 0, 1, 2. _ * 2 gives 0, 2, 4.
The Promise executor function runs synchronously when the Promise is constructed. 'executor' logs first, then resolve('done') is called (but .then() was never attached so nothing else logs), then 'after' logs.
Generator objects are iterable. Spreading a generator collects all its yielded values. range(1, 4) yields 1, 2, 3, 4 (i <= end), so the result is [1, 2, 3, 4].
The Proxy's set trap validates the value. p.a = 99 is a number, so Reflect.set applies it and p.a becomes 99. p.a = 'x' triggers the throw — caught and logged as 'Numbers only'.