Synchronous code runs first, so 1 and 4 print immediately. Then the microtask queue drains before the macrotask queue, so the resolved Promise callback (3) runs before the setTimeout callback (2) — even with a 0 ms delay. Microtasks always win.
Arrow functions have no `this` of their own — they inherit it from the enclosing scope. `greet` is called as a method so its `this` is `obj`, and the arrow function inherits that. Had `inner` been a regular function, `this` would have been undefined in strict mode.
`var` declarations are hoisted and initialised to undefined, so the first log succeeds. `let` is hoisted too but sits in the temporal dead zone until its declaration runs, so reading it early throws. That difference is the main practical reason to prefer `let`.
When `+` has a string on either side it concatenates rather than adds, converting the number to text. Every other arithmetic operator does the opposite and converts the string to a number — `5 - '5'` is 0.
function counter() {
let count = 0
return { inc: () => ++count, get: () => count }
}
const a = counter()
const b = counter()
a.inc(); a.inc(); b.inc()
console.log(a.get(), b.get())
Answer: 2 1
Each call to `counter()` creates a new scope, so `a` and `b` close over separate `count` bindings. This is the standard way to get private state in JavaScript — the variable is unreachable except through the returned functions.
`map` and `filter` both return new arrays and leave the original untouched — `arr` still has 3 elements. Knowing which array methods mutate (push, sort, splice, reverse) and which do not is the difference between predictable and surprising code.
`push` adds to the end, so length becomes 4. Arrays are zero-indexed, so `arr[0]` is 1. Reading past the end gives `undefined` rather than throwing — which is why an out-of-range read often shows up much later as a confusing error.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
Answer: 3 3 3
`var` is function-scoped, so all three callbacks close over the same binding. By the time the timeouts fire the loop has finished and `i` is 3. Swapping `var` for `let` creates a fresh binding per iteration and prints 0 1 2.
9. This should add two numbers but returns '12'. Which line is wrong?
Beginner
js
1 function add(a, b) {
2 return a + b
3 }
4 const total = add(prompt('x'), prompt('y'))
Answer: Line 4 — prompt returns strings, so + concatenates
`prompt` always yields a string, so `'1' + '2'` gives '12'. Convert first with `Number(...)` or `parseInt(..., 10)`. The same trap applies to values read from form inputs and URL parameters.
Answer: Line 3 — an async reducer makes the accumulator a Promise
An `async` callback always returns a Promise, so `reduce` produces a Promise as its accumulator on every pass. The code happens to still work if you await the result, but `sum` is a Promise, not a number. Since the items are already resolved by line 2, the reducer should be synchronous: `items.reduce((acc, item) => acc + item.price, 0)`.
11. This should debounce the handler, but it fires on every call. Which line is wrong?
Advanced
js
1 function debounce(fn, wait) {
2 return function (...args) {
3 let timer
4 clearTimeout(timer)
5 timer = setTimeout(() => fn(...args), wait)
6 }
7 }
Answer: Line 3 — timer is declared inside the returned function, so it resets each call
`timer` needs to live in the closure created by `debounce`, not inside the returned function. As written, every invocation gets a fresh `undefined` timer, so `clearTimeout` cancels nothing. Moving `let timer` to line 2's outer scope fixes it.
Answer: Line 1 — forEach ignores the returned promises, so 'done' logs first and order is not guaranteed
`forEach` discards whatever its callback returns, so it finishes immediately without waiting and 'done' prints before any user. Use `for (const id of ids) { await ... }` for sequential order, or `await Promise.all(ids.map(...))` to run them concurrently.
A `Set` stores only distinct values, and spreading it back into an array is the standard de-duplication idiom. `WeakSet` only accepts objects and is not iterable, so it cannot be spread.
`length` is writable on arrays, and lowering it truncates — the elements beyond the new length are deleted outright. Setting `length = 0` is an old idiom for emptying an array in place, which matters when other code holds a reference to it.
`==` coerces before comparing, so a number and a numeric string match; `===` also requires the same type. `null == undefined` is a deliberate special case in the spec — they are loosely equal to each other and to nothing else, which makes `x == null` a neat check for both.
Dot and bracket notation both read properties; bracket notation is the one that accepts a variable or a name with spaces. A missing property returns `undefined` rather than throwing — but reading a property *of* undefined does throw.
Spread copies one level deep. `name` is a primitive so the copy is independent, but `address` is a shared reference — mutating it is visible through both objects. Use `structuredClone` when you need a genuinely independent nested copy.
`typeof` returns a lowercase string naming the type. The famous exception is `typeof null`, which returns "object" — a bug from 1995 that cannot be fixed without breaking existing sites.
`+` coerces both operands to primitives. An array's primitive form is `join(',')`, giving "1,2,3" and "4,5", which then concatenate as strings. Use `concat` or spread to actually combine arrays.
A is synchronous so it runs first. B and D are both microtasks and run in the order they were queued — queueMicrotask came before the .then callback. C is a macrotask (timer) and runs only after the microtask queue is empty.
21. Fill in the blank so the variable cannot be reassigned.
Beginner
js
____ MAX_USERS = 100
// MAX_USERS = 200 would throw
Answer: const
`const` prevents reassignment of the binding. It does not freeze the value — the contents of a const object or array can still be changed. `let` allows reassignment, and `var` should generally be avoided in new code.
22. Fill in the blank so the default is used only when the value is null or undefined — not when it is 0.
Intermediate
js
const timeout = config.timeout ____ 3000
Answer: ??
`??` falls back only on null and undefined. `||` falls back on any falsy value, so a deliberate `timeout: 0` would be silently replaced by 3000 — the same bug hits empty strings and `false`.
class Base {
static create() { return new this() }
}
class Derived extends Base {}
console.log(Derived.create() instanceof Derived)
Answer: true
In a static method, `this` refers to the class the method was called on — `Derived`, not `Base`. So `new this()` constructs a `Derived`. This is how factory methods inherit correctly, and it is why `new this()` beats hard-coding `new Base()`.
The default sort converts elements to strings and compares them lexicographically, so "10" sorts before "2". Always pass a comparator for numbers: `sort((a, b) => a - b)`. Remember `sort` also mutates the original array.