- Which choice gives the complete output and correctly describes the object spread operation?
Answer: get, then 2 true — spread invokes the source getter and defines an own data property without calling the inherited target setter.
When you see object spread (...source) in JavaScript, the key question to ask is: how does spread actually copy properties? Spread uses Object.assign-like semantics — it reads source properties (invoking getters) and then defines own data properties directly on the target object, bypassing any setters on the target.
Here's the sequence in this question: { __proto__: proto, ...source } sets proto as the prototype, then spreads source. Spreading reads source.x, which triggers the getter and logs get, returning 2. JavaScript then uses Object.defineProperty internally to create an own data property x on target with value 2. This completely bypasses the inherited setter from proto. So target.x returns 2, and Object.hasOwn(target, 'x') returns true, making C the correct answer.
A is wrong because spread does not copy the getter function itself — it invokes the getter and copies the resulting value. B contains the biggest misconception: spread never routes assignment through a prototype's setter. Even though proto has a set x, the spread operation uses internal property definition, not the = assignment operator, so the setter is never called and set 2 is never logged. D is wrong for a similar reason — the value is not discarded. The inherited setter-only descriptor on proto is irrelevant because spread creates a new own property on target directly.
Your study tip: remember that object spread invokes getters on the source and defines data properties (not assignments) on the target — it skips both source and target accessor traps during the write phase.
- What is logged, and which sequence of initialization steps explains it?
Answer: [null,7] — the base constructor first stores undefined, the derived field is then initialized, and JSON converts the array's undefined to null.
When JavaScript instantiates a derived class, the order of operations matters enormously: super() runs first, then the derived class's field initializers execute, and finally the rest of the derived constructor's body runs. This question tests whether you understand that sequence — and a subtle JSON behavior on top of it.
Here's what actually happens step by step. When new Child() is called, super() invokes the Base constructor before Child's value = 7 initializer has run. At that moment, this.value is undefined, so this.log is initialized to [undefined]. Control returns to Child, the field initializer fires and sets this.value = 7, and then this.log.push(this.value) appends 7. The log ends up as the array [undefined, 7]. However, JSON.stringify converts undefined inside arrays to null — that's specified behavior, since JSON has no undefined token. So the output is [null,7], making D correct.
A is wrong because accessing an uninitialized field doesn't throw a ReferenceError — JavaScript simply returns undefined for properties that haven't been set yet. B correctly identifies that undefined is stored first but gets the JSON behavior wrong: JSON does not preserve undefined as the literal token undefined; it silently converts it to null in arrays. C is wrong because derived field initializers run after super(), not before — so this.value is not yet 7 when the base constructor executes.
As a study tip, remember this two-part trap: the super() → field initializer order, and that JSON.stringify([undefined]) yields [null]. Both halves must be correct to reach the right answer.
- What does the final statement log, and how do the assignment and deletion affect the prototype chain?
Answer: 1 false — compound assignment reads the inherited value and creates an own value of 3; deletion removes that own property, revealing the prototype's 1.
When working with JavaScript's prototype chain, the key question to ask is: where does a property live — on the object itself, or inherited from its prototype? This distinction governs how assignment, deletion, and lookup all behave.
Here's what happens step by step. Object.create(prototype) creates item with no own properties, inheriting score: 1 from prototype. When you write item.score += 2, JavaScript first reads item.score — which traverses the prototype chain and finds 1 — then assigns 1 + 2 = 3 as an own property on item. Now item has its own score: 3 that shadows the prototype's score: 1. When you delete item.score, that own property is removed. The prototype's score: 1 is untouched and now visible again through normal prototype lookup. So item.score logs 1, and Object.hasOwn(item, 'score') returns false because item no longer has an own property. That confirms B is correct.
A is wrong because compound assignment does not mutate the inherited property — it creates a new own property on item. Prototypes are not directly modified this way. C reflects a common misconception that deletion can "block" prototype properties. Deletion only removes own properties; once removed, prototype lookup resumes normally. D is wrong because deletion doesn't leave any marker or stub on the object — the own property is completely gone.
A useful rule of thumb: in JavaScript, delete only ever removes own properties, and += on an inherited value always writes a new own property rather than modifying the prototype.
- Which logging order results from the interaction of
await, exception handling, and promise reactions? Answer: sync, cleanup, xy — await suspends run; its rejection is caught later, finally runs, and the .then reaction follows settlement.
When you see async/await combined with try/catch/finally, think in two lanes: the synchronous lane (code that runs immediately) and the microtask lane (code scheduled after the current call stack clears).
When run() is called, it executes synchronously until it hits await. At that point, the async function suspends and control returns to the caller. That's why console.log('sync') — which sits after run().then(console.log) in the synchronous code — prints first. Nothing about calling an async function blocks the synchronous flow.
Once the call stack is clear, the microtask queue processes the awaited rejection. Because you wrote return await Promise.reject('x') (not just return Promise.reject('x')), the rejection is unwrapped inside the async function, which means the local catch block can intercept it. The catch returns 'x' + 'y', resolving the async function. Before that resolution propagates, the finally block runs, printing **cleanup**. Finally, the .then(console.log) reaction fires when the promise returned by run() settles, printing **xy**. So the order is sync, cleanup, xy — confirming B.
A is wrong because nothing about async/await throws synchronously — await always defers to the microtask queue, so cleanup cannot appear before sync. C is wrong because finally always runs before the enclosing async function's promise settles, not after. D is wrong because return await is precisely what enables the local catch to handle the rejection; without await, the rejection would escape.
Study tip: Remember that return await inside try/catch is meaningfully different from return — the extra await keeps the rejection inside the async function's error-handling scope.
- What does the final statement log, and why?
Answer: [1, 3, 4, 5] [2, 3, 4, 5] — forEach uses its initial length but reads each indexed element when that iteration occurs.
When forEach iterates over an array, it captures the array's length at the start of each iteration step, but it always reads the live element sitting at each index when that step actually executes. This distinction is what makes this question tricky.
Here's the trace: forEach begins with values = [1, 2, 3, 4] (length 4). At index 0, it reads values[0] → 1, pushes 1 to seen, then calls shift(). This removes the first element, so values becomes [2, 3, 4]. Now values[1] is 3, not 2. At index 1, forEach reads values[1] → 3, pushes 3 to seen, then calls push(5), making values = [2, 3, 4, 5]. At index 2, forEach reads values[2] → 4, pushes 4. At index 3, forEach reads values[3] → 5, pushes 5. Final result: seen = [1, 3, 4, 5] and values = [2, 3, 4, 5] — confirming A.
B is wrong because forEach does not snapshot element values upfront — it reads them live, which is exactly why shift() causes index 1 to return 3 instead of 2. C is wrong because the push(5) at index 1 extends the array length to 4 again, so index 3 is visited and 5 gets collected. D is wrong because the extra iteration happens due to push(5) restoring the length — forEach doesn't add a second extra iteration on top of that.
As a study tip, remember: **forEach locks in the length check per step, not upfront** — mutating the array mid-loop creates subtle index-shifting bugs that are a classic JavaScript interview trap.
- Which choice correctly explains the output?
Answer: 15 15 100 — the arrow function retains the this value from the call to counter.makeAdder(), despite being invoked through other.
When you see a question involving arrow functions returned from methods, the key concept to focus on is **lexical this binding**. Unlike regular functions, arrow functions do not get their own this — they permanently capture this from the surrounding scope at the moment they are defined, not when they are called.
Here, makeAdder() is called as counter.makeAdder(), so inside makeAdder, this refers to counter. The arrow function returned captures that this — meaning it will always point to counter, regardless of how or where it's later invoked. So when other.add(5) runs, this.value is counter.value (10), not other.value (100). After adding 5, counter.value becomes 15, and other.add(5) returns 15. other.value remains untouched at 100, giving you 15 15 100 — confirming A is correct.
B is wrong because it describes how regular functions behave — dynamic this binding based on the calling object. Arrow functions ignore the calling context entirely, so other has no influence on this.
C is wrong because there's no private closure variable here. The arrow function modifies a real, accessible property on counter via this.value, not some hidden internal value.
D is wrong because this is never lost or undefined. Arrow functions lock in their this at definition time, so extraction doesn't strip the binding the way it would with a regular function.
Study tip: Always ask yourself, "Is this an arrow function or a regular function?" Arrow functions = lexical this (look at where it was defined). Regular functions = dynamic this (look at how it's called).
- In which order are values logged, and what happens to the pending return?
Answer: finally, then try — the return expression is evaluated first, and finally executes before that saved return value is delivered.
When you see try...finally in JavaScript, the critical concept to internalize is that finally always runs — and it runs at a precise moment: after the try block completes but before the return value is actually delivered to the caller.
Here's what happens step by step in choose(): JavaScript evaluates return value inside try, which captures the current string 'try' as the pending return value. Before delivering that value, JavaScript enters the finally block, reassigns value to 'finally', and calls console.log(value) — printing 'finally' first. Then the saved return value 'try' is delivered to the outer console.log(choose()), printing 'try' second. So the output order is finally, then try — making D correct.
A is wrong because it describes finally as optional when no exception is thrown. That's completely false — finally always executes, even when a return statement is present. B is wrong because it misunderstands when the return expression is evaluated. JavaScript captures the return value ('try') before entering finally, so reassigning value afterward doesn't change what gets returned. C is wrong in the opposite direction — it implies the function fully returns to the caller before finally runs, which reverses the actual execution order.
A useful mental model: think of finally as a "checkpoint" the function must pass through before its return value is handed off. The value is already packed up; finally just gets one last chance to run code — not to change what was already packed.
- Which choice correctly gives the logging order and explains how the event loop produces it?
Answer: A, F, C, E, D, B — synchronous code finishes first, the queued microtasks run in FIFO order, and the timer callback runs afterward.
Whenever you see a question about JavaScript execution order, think in three layers: synchronous code, microtasks, and macrotasks (like timers). The event loop always fully drains the microtask queue before picking up the next macrotask.
Here's how the code actually runs. First, the synchronous statements execute top-to-bottom: console.log('A') fires immediately, then setTimeout schedules its callback as a macrotask (not yet run), then Promise.resolve().then(...) queues its callback as a microtask, then queueMicrotask(() => console.log('E')) queues another microtask, and finally console.log('F') executes. So synchronous output is A, F. Now the call stack is empty, so the engine drains the microtask queue in FIFO order. The Promise reaction runs first (it was queued first), printing C — and inside that callback, queueMicrotask(() => console.log('D')) enqueues D. But E was already sitting in the queue before D was added, so E runs next, then D. Finally, the macrotask queue is checked and the setTimeout callback runs, printing B. The full order is A, F, C, E, D, B, making A correct.
Choice B is wrong because it claims a microtask spawned inside a microtask jumps ahead of already-queued microtasks — it doesn't; D is appended to the end of the current queue, after E. Choice C incorrectly claims queueMicrotask has higher priority than Promise reactions; both use the same microtask queue with no priority distinction. Choice D is wrong because the engine doesn't pause after each console.log to drain microtasks — it waits until the entire synchronous call stack is clear.
Study tip: Visualize two buckets — microtasks and macrotasks. Synchronous code fills the stack; once empty, drain the microtask bucket completely (including any newly added microtasks in FIFO order) before ever touching the macrotask bucket.
- Which choice correctly describes the values logged by this code?
Answer: 3 7 — the first default reads the outer x, and each get closure later reads the corresponding mutated parameter a.
This question tests two subtle JavaScript concepts working together: default parameter scoping and closure behavior over mutable bindings.
When JavaScript evaluates default parameters, they live in their own intermediate scope — between the outer (global) scope and the function body. Critically, the let x = 10 inside the function body is not visible to default parameter expressions. So when read() is called with no arguments, a defaults to the outer x, which is 1. Then a += 2 mutates a to 3. The default get = () => a creates a closure over the parameter binding a itself — not a snapshot of its initial value. So get() returns the current value of a, which is 3. When read(5) is called, a starts at 5, becomes 7 after a += 2, and get() again returns the live binding, yielding 7. The output is 3 7, confirming A is correct.
B is wrong because it claims the body's let x = 10 is visible to the default parameter — it isn't. Default parameters cannot see variables declared inside the function body. C is wrong because it assumes the closure captures a's value at creation time (like a snapshot). Closures capture the variable binding, not a frozen value, so they reflect later mutations. D is wrong because it suggests explicitly passing an argument somehow "freezes" it before mutation — but a += 2 mutates the binding regardless, and get() reads it after that mutation.
Your study tip: always remember that closures close over bindings, not values — and default parameters live in a scope that sees the outer environment, never the function body.
- Which choice correctly explains the two Boolean results?
Answer: true false — ![] is false; loose equality coerces the empty array and false to numeric zero, while strict equality rejects their differing types.
When JavaScript compares values with ==, it applies type coercion rules defined in the spec — and those rules can be surprising. Seeing [] == ![] should immediately signal: "what types are being compared, and how will JS coerce them?"
Here's the step-by-step logic that makes C correct. First, ![] is evaluated: since arrays are truthy, negating one gives false. Now you have [] == false. When == sees an object on one side and a Boolean on the other, it converts the Boolean to a number first (false → 0), then converts the object to a primitive ([] → "" → 0). You end up comparing 0 == 0, which is true. For strict equality (===), no coercion happens at all — [] is an object and false is a Boolean, so differing types immediately return false. Result: true false.
A is wrong because it claims both comparisons return true and misidentifies the coercion path — === never converts anything, so it can't produce true here. B is wrong because it conflates an array being truthy with the array evaluating to true in a comparison; that's not how == works, and the result isn't false false. D is completely backwards — == does not compare object identities (that's ==='s job with reference types), and === never converts operands to Boolean values.
As a study tip, memorize the == coercion order: Boolean → Number → then the other operand. That single rule untangles most "weird equality" questions you'll encounter.