Skip to content
RED SAGSkills

JavaScript interview questions and answers

JavaScript rounds test how well you understand the language itself, not just a framework. Expect questions on scope, closures, the event loop, promises and how values are compared and copied, often as "what does this code print?" puzzles.

Below are 60 JavaScript questions with the correct answer and a short explanation, grouped from fresher to experienced level. Try each one before you open the answer.

What JavaScript interviews usually check

  • ✓Scope, hoisting, closures and the value of this
  • ✓The event loop: call stack, microtasks, macrotasks and async/await
  • ✓Equality, type coercion, and copying objects and arrays
  • ✓Array methods, destructuring, spread and modules
  • ✓Error handling and common bugs in asynchronous code

JavaScript interview questions for freshers easy

1. typeof null and arrays

What does this print?

console.log(typeof null, typeof [], typeof undefined)
  1. A.null array undefined
  2. B.object object undefined
  3. C.null object undefined
  4. D.object array undefined
Show answer

Answer: B. object object undefined

typeof null returns "object" because of an old bug in the first version of JavaScript that can never be fixed. Arrays are objects, so typeof [] is also "object". Use value === null to check for null and Array.isArray(value) to check for arrays.

2. Changing a property of a const object

What does this print?

const user = { name: 'Ana' }
user.name = 'Ben'
console.log(user.name)
  1. A.A TypeError, because user is const
  2. B.Ana
  3. C.Ben
  4. D.undefined
Show answer

Answer: C. Ben

const only stops the variable from being reassigned to a different value. The object it points to can still be changed, so updating user.name works and prints Ben. Writing user = {} would throw, and Object.freeze is needed to make the object itself read-only.

3. ?? versus || with zero

What does this print?

const a = 0 || 10
const b = 0 ?? 10
console.log(a, b)
  1. A.10 10
  2. B.0 0
  3. C.10 0
  4. D.0 10
Show answer

Answer: C. 10 0

|| falls back for any falsy value, and 0 is falsy, so a becomes 10. ?? only falls back when the left side is null or undefined, so b keeps 0. Use ?? for defaults when 0, an empty string or false are valid values.

4. Plus and minus with a string

What do these two lines print?

console.log("5" + 3)
console.log("5" - 3)
  1. A."53" and 2
  2. B.8 and 2
  3. C."53" and "2"
  4. D.8 and NaN
Show answer

Answer: A. "53" and 2

When either side of + is a string, + joins strings, so "5" + 3 is "53". The - operator only works on numbers, so "5" is converted to 5 and the result is 2. Convert input with Number() before adding to avoid this bug.

5. When a default parameter applies

What does this code print?

function greet(name = "Guest") {
  return "Hi " + name
}
console.log(greet(undefined), "|", greet(null))
  1. A.Hi Guest | Hi Guest
  2. B.Hi undefined | Hi null
  3. C.Hi Guest | Hi null
  4. D.Hi undefined | Hi Guest
Show answer

Answer: C. Hi Guest | Hi null

A default parameter is used only when the argument is undefined (or missing). null is a real value, so it is passed through and joined as "null". If null should also fall back, use name ?? "Guest" inside the function.

6. Rename and default in destructuring

What does this code print?

const user = { name: "Asha" }
const { name: fullName, city = "Dubai" } = user
console.log(fullName, city)
  1. A.undefined Dubai
  2. B.Asha undefined
  3. C.It throws, because city is not in user
  4. D.Asha Dubai
Show answer

Answer: D. Asha Dubai

name: fullName reads the name property into a new variable called fullName. city = "Dubai" gives a default because user.city is undefined. Destructuring a missing property is not an error; it just gives undefined or the default.

7. Typo in a variable name

In an ES module (which is always strict mode), a developer writes totl = 100 by mistake, where only total was declared. What happens?

  1. A.A new global variable totl is created
  2. B.total is set to 100
  3. C.The line is silently ignored
  4. D.A ReferenceError is thrown
Show answer

Answer: D. A ReferenceError is thrown

In strict mode, assigning to a name that was never declared throws a ReferenceError. In old sloppy scripts the same typo silently created a global variable, which hid bugs. Modules and class bodies are strict by default.

8. Changing one letter of a string

In a classic non-module script (sloppy mode), what does this print?

let s = "cat"
s[0] = "b"
console.log(s)
  1. A.bat
  2. B.b
  3. C.cat
  4. D.It throws a TypeError in all modes
Show answer

Answer: C. cat

Strings in JavaScript are immutable, so assigning to an index does nothing (in strict mode it throws a TypeError). To change a letter, build a new string, for example "b" + s.slice(1).

9. Hiding a spinner with finally

Where should you hide a loading spinner so it hides whether the request succeeds or fails?

  1. A.In .finally() or a finally block
  2. B.Only in .then()
  3. C.Only in .catch()
  4. D.In a setTimeout after the request starts
Show answer

Answer: A. In .finally() or a finally block

finally runs after the promise settles either way and passes the original result or error through unchanged. Putting the cleanup there avoids repeating it in both the success and error paths.

10. A rejection nobody handles

A promise rejects and no code calls .catch or awaits it in try/catch. What happens in the browser?

  1. A.The page reloads itself automatically to recover
  2. B.The error is silently lost and leaves no trace
  3. C.An unhandledrejection event fires and it is logged
  4. D.The promise retries itself once before failing
Show answer

Answer: C. An unhandledrejection event fires and it is logged

Browsers fire an unhandledrejection event on window and log the error to the console. Error trackers listen for this event. In Node.js, unhandled rejections crash the process by default.

11. Forgetting to await response.json

What does data hold here?

const res = await fetch("/api/user")
const data = res.json()
console.log(data.name)
  1. A.The parsed user object itself
  2. B.The raw JSON text of the body
  3. C.A Promise, so data.name is undefined
  4. D.The Response object again
Show answer

Answer: C. A Promise, so data.name is undefined

res.json() reads and parses the body, which takes time, so it returns a promise. Without await, data is the promise and data.name is undefined. Write const data = await res.json().

12. Where to run a heavy calculation

An invoice page must parse a 40 MB CSV in the browser without freezing the UI. What is the best tool?

  1. A.A Web Worker
  2. B.A setTimeout of 0
  3. C.An async function
  4. D.A Promise.all
Show answer

Answer: A. A Web Worker

An async function still runs its synchronous parts on the main thread, and timers only delay the work. A Web Worker runs on a separate thread, so heavy parsing does not block clicks or rendering. Results come back with postMessage.

13. Declaring the same interface twice

Two declarations of interface User exist in the same scope, one with name and one with email. What is the result?

  1. A.They merge into one User with both properties
  2. B.A duplicate identifier error
  3. C.The second declaration replaces the first
  4. D.The first declaration wins and email is ignored
Show answer

Answer: A. They merge into one User with both properties

Interfaces support declaration merging: same-named interfaces in one scope are combined. A type alias with the same name twice is an error instead. Merging is how libraries let you add fields to types such as Window.

14. slice vs splice

Given const a = [1, 2, 3, 4, 5], which call changes a itself?

  1. A.a.slice(1, 3)
  2. B.a.splice(1, 2)
  3. C.a.concat([6])
  4. D.a.map((x) => x * 2)
Show answer

Answer: B. a.splice(1, 2)

splice removes or inserts elements in place and returns the removed items, so a becomes [1, 4, 5]. slice, concat and map all return new arrays and leave the original untouched.

15. new Array(3) then map

What is the result of this expression?

new Array(3).map(() => 0)
  1. A.An array of 3 empty slots
  2. B.[0, 0, 0]
  3. C.[undefined, undefined, undefined]
  4. D.[]
Show answer

Answer: A. An array of 3 empty slots

new Array(3) creates an array of length 3 with holes, not with undefined values. map skips holes, so the result is still three empty slots. Use new Array(3).fill(0) or Array.from({ length: 3 }, () => 0).

16. join with null values

What does [1, null, 2, undefined].join('-') return?

  1. A.'1--2-'
  2. B.'1-null-2-undefined'
  3. C.'1-2'
  4. D.'1-0-2-0'
Show answer

Answer: A. '1--2-'

join converts null and undefined to empty strings, so their slots become empty fields between the separators. This can hide missing data in a CSV export, so filter or replace them first.

17. Number keys in a plain object

What does this print?

const o = {}
o[1] = 'a'
o['1'] = 'b'
console.log(Object.keys(o).length, o[1])
  1. A.2 and 'a'
  2. B.1 and 'b'
  3. C.2 and 'b'
  4. D.1 and 'a'
Show answer

Answer: B. 1 and 'b'

Plain object keys are strings (or symbols), so the number 1 is converted to '1' and both writes hit the same key. The second write wins. A Map keeps 1 and '1' as different keys.

18. Checking for NaN

A price parser returns NaN for bad input. Which check reliably detects it in JavaScript?

  1. A.price === NaN
  2. B.price == NaN
  3. C.Number.isNaN(price)
  4. D.price === undefined
Show answer

Answer: C. Number.isNaN(price)

NaN is the only value that is not equal to itself, so price === NaN is always false. Number.isNaN returns true only for the NaN value itself. The older global isNaN first converts its argument, so isNaN('abc') is also true.

19. JavaScript default sort on numbers

What does this JavaScript code print?

const marks = [10, 9, 1, 100]
marks.sort()
console.log(marks)
  1. A.[1, 9, 10, 100]
  2. B.[1, 10, 100, 9]
  3. C.[100, 10, 9, 1]
  4. D.[10, 9, 1, 100]
Show answer

Answer: B. [1, 10, 100, 9]

With no comparator, Array.prototype.sort converts items to strings and compares them by UTF-16 code units. As strings, "1" < "10" < "100" < "9". Use marks.sort((a, b) => a - b) to sort numbers by value.

20. Comparing two strings

How do you check whether two string memory values are equal in Solidity?

  1. A.a == b, like any other value type
  2. B.a.equals(b) from the string type
  3. C.bytes32(a) == bytes32(b) for any length
  4. D.keccak256(bytes(a)) == keccak256(bytes(b))
Show answer

Answer: D. keccak256(bytes(a)) == keccak256(bytes(b))

Solidity has no == operator for strings. The common approach is comparing the keccak256 hashes of their bytes, which works for any length. Converting to bytes32 truncates longer strings. Newer helpers like OpenZeppelin's Strings.equal do the same under the hood.

JavaScript intermediate interview questions medium

21. Arrow function as an object method

This code runs as an ES module. What does it print?

const counter = {
  count: 5,
  show: () => console.log(this?.count),
  print() { console.log(this.count) },
}
counter.show()
counter.print()
  1. A.5, then 5
  2. B.undefined, then 5
  3. C.5, then undefined
  4. D.A TypeError, then 5
Show answer

Answer: B. undefined, then 5

Arrow functions do not get their own this; they use the this of the surrounding scope. At the top level of a module this is undefined, so this?.count gives undefined. print is a normal method, so when called as counter.print() its this is counter and it prints 5.

22. One failed request breaks the dashboard

A dashboard loads three widgets with Promise.all. When one API fails, the whole page shows an error even though two widgets loaded fine. Which method lets each widget show its own result or error?

  1. A.Promise.race
  2. B.Promise.any
  3. C.Promise.allSettled
  4. D.Promise.resolve
Show answer

Answer: C. Promise.allSettled

Promise.all rejects as soon as any promise rejects, so you lose the successful results. Promise.allSettled waits for all of them and gives an array of { status, value } or { status, reason } objects. Promise.any resolves with only the first success, and Promise.race settles with whichever finishes first.

23. Function hoisting and let

What happens when this script runs?

console.log(sayHi())
console.log(total)

function sayHi() { return 'hi' }
let total = 5
  1. A.It prints hi, then undefined
  2. B.It throws a ReferenceError on the first line
  3. C.It prints undefined, then 5
  4. D.It prints hi, then throws a ReferenceError
Show answer

Answer: D. It prints hi, then throws a ReferenceError

Function declarations are hoisted with their body, so sayHi can be called before the line where it is written. let variables are hoisted too, but they stay in the temporal dead zone until their declaration runs. Reading total before that throws "Cannot access total before initialization".

24. Two counters from one factory

What does this print?

function makeCounter() {
  let n = 0
  return () => ++n
}
const a = makeCounter()
const b = makeCounter()
a(); a()
console.log(a(), b())
  1. A.3 4
  2. B.1 1
  3. C.3 3
  4. D.3 1
Show answer

Answer: D. 3 1

Each call to makeCounter creates a new n, and the returned arrow function keeps its own n alive through a closure. a has been called three times, so it returns 3, while b has its own n and returns 1 on its first call.

25. Freezing a nested config

What does this print?

const cfg = Object.freeze({ tax: { gst: 18 } })
cfg.tax.gst = 5
console.log(cfg.tax.gst)
  1. A.18
  2. B.undefined
  3. C.It always throws a TypeError
  4. D.5
Show answer

Answer: D. 5

Object.freeze only freezes the object’s own properties. cfg.tax cannot be replaced, but the nested object it points to is not frozen, so its gst can change. To lock everything, freeze each nested object too (a deep freeze).

26. Number versus parseInt on "12px"

What do these return?

Number("12px")
parseInt("12px", 10)
Number("")
  1. A.12, 12 and NaN
  2. B.NaN, 12 and 0
  3. C.NaN, NaN and 0
  4. D.NaN, 12 and NaN
Show answer

Answer: B. NaN, 12 and 0

Number needs the whole string to be numeric, so "12px" gives NaN, but an empty or blank string becomes 0. parseInt reads digits from the start and stops at the first non-digit, so it returns 12. The empty-string case is a common form validation bug.

27. Return value with finally

What does this function return?

function f() {
  try {
    return "try"
  } finally {
    console.log("cleanup")
  }
}
console.log(f())
  1. A.It logs "cleanup", then "try"
  2. B.It logs "try", then "cleanup"
  3. C.It logs only "try"; finally is skipped after return
  4. D.It logs "cleanup", then undefined
Show answer

Answer: A. It logs "cleanup", then "try"

finally always runs, even when try returns. The return value "try" is held while finally runs, so "cleanup" is logged first, then the caller logs "try". If finally itself had a return, it would replace the try value.

28. Total wait with exponential backoff

A payment status call retries up to 3 times after the first failure, waiting 500 ms before the first retry and doubling each time. If every attempt fails, how long does the code spend waiting in total (ignoring request time)?

  1. A.1,500 ms
  2. B.2,000 ms
  3. C.3,500 ms
  4. D.7,500 ms
Show answer

Answer: C. 3,500 ms

The waits are 500 ms, then 1,000 ms, then 2,000 ms before retries 1, 2 and 3. 500 + 1,000 + 2,000 = 3,500 ms. Adding random jitter to each wait stops many clients from retrying at the same moment.

29. nextTick versus promise in Node

In Node.js (CommonJS script), what is the log order?

Promise.resolve().then(() => console.log("promise"))
process.nextTick(() => console.log("tick"))
console.log("sync")
  1. A.sync, promise, tick
  2. B.tick, sync, promise
  3. C.promise, tick, sync
  4. D.sync, tick, promise
Show answer

Answer: D. sync, tick, promise

Synchronous code runs first. After it, Node drains the process.nextTick queue before the promise microtask queue, so "tick" comes before "promise". Heavy use of nextTick can starve I/O, so queueMicrotask or promises are usually preferred.

30. Adding a timeout to fetch

fetch has no timeout option. What is the simplest modern way to fail a request after 5 seconds?

  1. A.fetch(url, { timeout: 5000, abort: true })
  2. B.setTimeout(() => fetch(url), 5000) before calling
  3. C.fetch(url, { signal: AbortSignal.timeout(5000) })
  4. D.await Promise.all([fetch(url), 5000])
Show answer

Answer: C. fetch(url, { signal: AbortSignal.timeout(5000) })

AbortSignal.timeout(ms) returns a signal that aborts on its own after the given time, rejecting fetch with a TimeoutError. It is supported in all modern browsers and Node. Combine it with other signals using AbortSignal.any if the user can also cancel.

31. Narrowing on a kind field

Inside case "card", which properties can you read on p without an error?

type Payment =
  | { kind: "upi"; vpa: string }
  | { kind: "card"; last4: string; network: string }

function label(p: Payment) {
  switch (p.kind) {
    case "card": /* here */
  }
}
  1. A.only kind
  2. B.kind, last4 and network
  3. C.kind and vpa
  4. D.kind, vpa, last4 and network
Show answer

Answer: B. kind, last4 and network

Each member of the union has a literal kind, which acts as a discriminant. Checking p.kind === "card" narrows p to the card member, so its last4 and network fields become available, while vpa does not.

32. Indexing with noUncheckedIndexedAccess

With "noUncheckedIndexedAccess": true, what is the type of rate in const rate = rates["dubai"], where rates is Record<string, number>?

  1. A.number
  2. B.number | undefined
  3. C.unknown | number
  4. D.number | null
Show answer

Answer: B. number | undefined

Without the flag, TypeScript assumes every index exists and gives number. With it, index reads on arrays and string-keyed records add undefined, because the key may be missing. This catches out-of-range and missing-key bugs.

33. Removing a row from state

Which code removes the order with id 42 from orders state without mutating it?

  1. A.orders.splice(index, 1); setOrders(orders)
  2. B.delete orders[index]; setOrders(orders)
  3. C.setOrders(orders.find((o) => o.id !== 42))
  4. D.setOrders(orders.filter((o) => o.id !== 42))
Show answer

Answer: D. setOrders(orders.filter((o) => o.id !== 42))

filter returns a new array without the matching item, which is the immutable way to delete. splice and delete change the original array, and find returns a single item, not an array.

34. Copying an array of order objects

What does this print?

const orders = [{ id: 1, status: 'new' }]
const copy = [...orders]
copy[0].status = 'paid'
console.log(orders[0].status)
  1. A.'new'
  2. B.undefined
  3. C.'paid'
  4. D.It throws an error
Show answer

Answer: C. 'paid'

Spread makes a new outer array but copies element references, so both arrays point to the same object. Changing a property through copy changes it for orders too. Use structuredClone or map with object spread for a deep enough copy.

35. Checking that two Sets are equal

a and b are Sets of strings. Why is a === b the wrong test for "same contents", and what works?

  1. A.=== compares references; check size and every item of a in b
  2. B.=== compares contents but ignores insertion order
  3. C.=== works only for Sets of numbers
  4. D.Sets cannot be compared without converting to JSON
Show answer

Answer: A. === compares references; check size and every item of a in b

Objects, including Sets, are equal under === only if they are the same object. For contents, check a.size === b.size and that every value of a is in b, which is O(n). Modern engines also offer a.isSubsetOf(b) with a size check.

36. What -1 >>> 0 gives

In JavaScript, what is the value of `-1 >>> 0`?

  1. A.-1
  2. B.0
  3. C.4294967295
  4. D.2147483647
Show answer

Answer: C. 4294967295

-1 in 32-bit two's complement is all 32 bits set. The unsigned right shift >>> treats the result as an unsigned 32-bit integer, so a shift of 0 just reinterprets those bits as 2^32 - 1 = 4294967295. x >>> 0 is a common way to get an unsigned 32-bit view of a number.

37. Comparator that returns a boolean

A developer writes this to sort numbers. Why is it a bug?

scores.sort((a, b) => a > b)
  1. A.Booleans cannot be returned from arrow functions
  2. B.It sorts in descending order instead of ascending
  3. C.It throws a TypeError for numbers above 100
  4. D.It never returns a negative value, so the order is unreliable
Show answer

Answer: D. It never returns a negative value, so the order is unreliable

The comparator must return negative, zero or positive. true and false become 1 and 0, so "a should come first" is never signalled. Results depend on the engine and input, and often look almost right. Use (a, b) => a - b.

38. Sorting customer names correctly

A UAE app sorts customer names that include accents and mixed case, such as "élodie", "Zara" and "aisha". Which JavaScript approach gives a natural alphabetical order?

  1. A.names.sort() with no comparator
  2. B.names.sort((a, b) => a.length - b.length)
  3. C.names.sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0))
  4. D.names.sort(new Intl.Collator('en').compare)
Show answer

Answer: D. names.sort(new Intl.Collator('en').compare)

Default sort compares UTF-16 code units, so all uppercase letters come before lowercase and accented letters go to the end. Intl.Collator (or localeCompare) applies language rules. A shared Collator is faster than calling localeCompare on every comparison.

39. Type cast on fetch response

The backend renamed fullName to displayName. tsc still passes, but the frontend crashes in production. Why didn’t TypeScript catch it?

type User = { id: string; fullName: string }

const res = await fetch('/api/me')
const user = (await res.json()) as User
render(user.fullName.toUpperCase())
  1. A.Types are erased at runtime and "as" is unchecked; validate data at the boundary
  2. B.tsc does not check files that use fetch
  3. C.The User type should be an interface, because type aliases are not checked against JSON
  4. D.res.json() always returns a string
Show answer

Answer: A. Types are erased at runtime and "as" is unchecked; validate data at the boundary

res.json() returns unknown data and "as User" tells the compiler to believe you without any check. Nothing about types exists at runtime, so a changed API shape only shows up as a crash. Parsing responses with a schema, or generating types from a shared contract such as OpenAPI or tRPC, makes the mismatch visible.

40. When @escaping is needed

Why must the completion parameter be marked @escaping here?

var handlers: [() -> Void] = []

func onFinish(_ completion: @escaping () -> Void) {
    handlers.append(completion)
}
  1. A.It is called many times inside the function
  2. B.It is stored and called after the function returns
  3. C.It throws errors to the caller
  4. D.It runs on the main thread
Show answer

Answer: B. It is stored and called after the function returns

Closures are non-escaping by default, meaning they cannot outlive the call. Storing one in a property or calling it later (for example in a network callback) needs @escaping, which also makes you think about self capture.

JavaScript interview questions for experienced candidates hard

41. Order of object keys

What does this print?

const o = { b: 1, 2: 1, a: 1, 1: 1 }
console.log(Object.keys(o))
  1. A.['b', '2', 'a', '1']
  2. B.['1', '2', 'b', 'a']
  3. C.['a', 'b', '1', '2']
  4. D.['b', 'a', '1', '2']
Show answer

Answer: B. ['1', '2', 'b', 'a']

Property order is defined by the spec: integer-like keys come first in ascending numeric order, then string keys in insertion order. So 1 and 2 come first, followed by b and a. If you need a reliable insertion order for any key, including numeric IDs, use a Map.

42. When extra properties are an error

Which line or lines does TypeScript report as an error?

type User = { name: string }

const data = { name: 'Ana', age: 30 }
const a: User = data                       // line A
const b: User = { name: 'Ana', age: 30 }   // line B
  1. A.Both line A and line B
  2. B.Only line B
  3. C.Only line A
  4. D.Neither line, because TypeScript is structurally typed
Show answer

Answer: B. Only line B

TypeScript is structural, so a value with extra properties is still assignable to User, which makes line A fine. But fresh object literals get an extra "excess property check" that flags unknown properties as likely typos, so line B errors. This is why a misspelled option in an inline object is caught, while the same object passed through a variable is not.

43. Symbol keys and JSON

What does this print?

const id = Symbol("id")
const user = { name: "Ravi", [id]: 42 }
console.log(Object.keys(user), JSON.stringify(user))
  1. A.[ "name" ] {"name":"Ravi"}
  2. B.[ "name", "id" ] {"name":"Ravi","id":42}
  3. C.[ "name" ] {"name":"Ravi","Symbol(id)":42}
  4. D.[ "name", "Symbol(id)" ] {"name":"Ravi"}
Show answer

Answer: A. [ "name" ] {"name":"Ravi"}

Symbol-keyed properties are skipped by Object.keys, for...in and JSON.stringify. They can be read with Object.getOwnPropertySymbols or Reflect.ownKeys. This makes symbols useful for metadata that should not leak into serialised data.

44. Object.is and negative zero

What does this print?

console.log(-0 === 0, Object.is(-0, 0), Object.is(NaN, NaN))
  1. A.true true true
  2. B.false false true
  3. C.true false false
  4. D.true false true
Show answer

Answer: D. true false true

Strict equality treats -0 and +0 as equal and NaN as unequal to itself. Object.is uses the SameValue rule, which separates -0 from +0 and treats NaN as equal to NaN. React uses Object.is to decide whether state has changed.

45. A promise awaited too late

In Node.js, slowTask takes 2 s and failingTask rejects after 100 ms. Why can the process crash with an unhandled rejection?

const a = slowTask()
const b = failingTask()
await a
await b // inside try/catch
  1. A.await can only be used once in each async function
  2. B.try/catch does not work with await when running in Node
  3. C.b rejects while the code still waits on a, with no handler yet
  4. D.slowTask blocks the whole event loop for the full 2 s
Show answer

Answer: C. b rejects while the code still waits on a, with no handler yet

A rejection is checked for a handler soon after it happens. b rejects at 100 ms, but nothing has attached to b yet because the code is still waiting on a. Node treats it as unhandled and exits. Use await Promise.all([a, b]) so both have handlers at once.

46. Breaking out of for await

An async generator opens a database cursor and closes it in a finally block. What happens when the loop breaks early?

async function* rows() {
  const cur = await db.openCursor()
  try {
    for await (const r of cur) yield r
  } finally {
    await cur.close()
  }
}
for await (const r of rows()) {
  if (r.id === 10) break
}
  1. A.break calls return() on the generator, so finally runs
  2. B.The cursor stays open until garbage collection happens
  3. C.break throws an error inside the generator body
  4. D.The loop keeps going until all rows have been read
Show answer

Answer: A. break calls return() on the generator, so finally runs

When a for...of or for await...of loop exits early with break, return or throw, it calls the iterator’s return() method. For a generator this resumes it as if a return happened at the yield, so finally blocks run and resources are released.

47. Progress bar that never moves

This code updates a progress bar 100 times while processing items, but the user only sees 100% at the end. Why?

for (let i = 1; i <= 100; i++) {
  processItem(i) // 30 ms of sync work
  bar.style.width = i + "%"
}
  1. A.style.width ignores values below 100%
  2. B.The loop runs too fast for a 60 Hz screen
  3. C.Percent widths need a transition to show
  4. D.The browser paints only after the whole task finishes
Show answer

Answer: D. The browser paints only after the whole task finishes

Rendering happens between tasks, not during one. The loop is one long 3-second task, so the browser cannot paint until it ends. Process chunks and yield with await scheduler.yield() or setTimeout so the bar can repaint, or use a Worker.

48. setImmediate inside an I/O callback

In Node.js, what is the order inside a file read callback?

fs.readFile("a.txt", () => {
  setTimeout(() => console.log("timeout"), 0)
  setImmediate(() => console.log("immediate"))
})
  1. A.immediate, then timeout, every time
  2. B.timeout, then immediate, every time
  3. C.It varies between runs
  4. D.Only immediate is printed
Show answer

Answer: A. immediate, then timeout, every time

After the poll (I/O) phase, Node’s event loop goes to the check phase, where setImmediate callbacks run, before looping back to the timers phase. So inside an I/O callback, immediate always comes first. At the main module top level, the order can vary.

49. Time with a concurrency limit

You upload 100 invoices. Each upload takes 200 ms, and a pool runs at most 5 at a time. Roughly how long does the whole batch take?

  1. A.20 seconds
  2. B.200 ms
  3. C.4 seconds
  4. D.1 second
Show answer

Answer: C. 4 seconds

With 5 running at once, 100 uploads need 100 ÷ 5 = 20 rounds. Each round takes about 200 ms, so 20 × 200 ms = 4,000 ms. Unlimited Promise.all would start all 100 at once and may hit rate limits or browser connection caps.

50. Promise.resolve of a promise

p is a native promise. What does Promise.resolve(p) === p print?

  1. A.false
  2. B.true
  3. C.undefined
  4. D.It throws a TypeError
Show answer

Answer: B. true

If the argument is already a native promise created by the same Promise constructor, Promise.resolve returns it unchanged. For other thenables or values it creates a new promise. new Promise(r => r(p)) would always make a new one.

51. Extracting a type with infer

What type does Item resolve to?

type ElementOf<T> = T extends readonly (infer U)[] ? U : never
const cart = [{ sku: "A1", qty: 2 }]
type Item = ElementOf<typeof cart>
  1. A.{ sku: string; qty: number }
  2. B.{ sku: string; qty: number }[]
  3. C.never
  4. D.unknown
Show answer

Answer: A. { sku: string; qty: number }

infer U declares a type variable that TypeScript fills by matching T against the pattern. typeof cart is { sku: string; qty: number }[], which matches an array of U, so U is the object type. Non-array types would give never.

52. Removing optional and readonly

What does this mapped type produce for { readonly id?: string }?

type Mutable<T> = { -readonly [K in keyof T]-?: T[K] }
  1. A.{ readonly id: string }
  2. B.{ id?: string }
  3. C.{ id: string }
  4. D.{ id: string | undefined }
Show answer

Answer: C. { id: string }

In a mapped type, -readonly removes the readonly modifier and -? removes optionality from every property. The built-in Required<T> uses -?, and a + prefix (or none) adds modifiers. The result is a plain required, writable id.

53. What the {} type accepts

Which assignment is a compile error?

let a: {} = 42
let b: {} = "hi"
let c: object = 42
let d: unknown = null
  1. A.Only c
  2. B.Only a and b
  3. C.a, b and c
  4. D.All four
Show answer

Answer: A. Only c

The {} type means "any value except null and undefined", so numbers and strings fit. object means non-primitive values only, so 42 is rejected. unknown accepts everything, including null.

54. Old value after an await

The user types more while save() is running. What does the alert show?

async function handleSave() {
  await save(text)
  alert("Saved: " + text)
}
  1. A.The text from the render when the click happened
  2. B.The latest text typed during the save
  3. C.An empty string
  4. D.undefined, because state is cleared by await
Show answer

Answer: A. The text from the render when the click happened

The handler closes over text from the render it belongs to. Later renders create new handlers with new text, but this running function still sees its own snapshot. Read the latest value from a ref if you really need it.

55. A custom hook with a stale callback

useCart returns addItem, but addItem always adds to the cart as it was on the first render. What is the bug?

function useCart() {
  const [items, setItems] = useState([])
  const addItem = useCallback((it) => {
    setItems([...items, it])
  }, [])
  return { items, addItem }
}
  1. A.useCallback must never be used inside custom hooks
  2. B.items must be stored in a ref instead of in state
  3. C.It keeps the first items; use setItems((prev) => [...prev, it])
  4. D.setItems cannot receive arrays, only single items
Show answer

Answer: C. It keeps the first items; use setItems((prev) => [...prev, it])

With an empty dependency array, the callback is created once and keeps the items from the first render. Using the functional update form reads the latest state at update time, so the callback can stay stable without listing items.

56. Removing items while looping forward

This code should remove every cancelled order but leaves one behind for [c, c, ok] (c = cancelled). Why?

for (let i = 0; i < orders.length; i++) {
  if (orders[i].cancelled) orders.splice(i, 1)
}
  1. A.splice does not update the array length property
  2. B.The loop condition is evaluated only once at the start
  3. C.After splice the next item shifts into index i and is skipped
  4. D.splice removes two items instead of one here
Show answer

Answer: C. After splice the next item shifts into index i and is skipped

Removing index 0 moves the second cancelled order into index 0, but the loop then increments i to 1 and never checks it. Loop backwards, decrement i after a splice, or better, use filter to build a new array in O(n).

57. NaN and -0 as Map keys

After m.set(NaN, 'a') and m.set(0, 'b'), what do m.get(NaN) and m.get(-0) return?

  1. A.undefined and 'b'
  2. B.'a' and undefined
  3. C.undefined and undefined
  4. D.'a' and 'b'
Show answer

Answer: D. 'a' and 'b'

Map keys use SameValueZero: NaN is treated as equal to NaN, and +0 and -0 count as the same key. This differs from ===, where NaN !== NaN.

58. Binary search loop that never ends

This search finds the last index where ok(i) is true. When does it loop forever?

while (lo < hi) {
  const mid = Math.floor((lo + hi) / 2)
  if (ok(mid)) lo = mid
  else hi = mid - 1
}
  1. A.When lo and hi are both zero
  2. B.When ok is false for every index
  3. C.When hi = lo + 1 and ok(lo) is true
  4. D.When the array has an even length
Show answer

Answer: C. When hi = lo + 1 and ok(lo) is true

With hi = lo + 1, mid rounds down to lo. If ok(lo) is true, lo = mid changes nothing, and the loop repeats. When the update is lo = mid, round the midpoint up: mid = lo + Math.ceil((hi - lo) / 2).

59. One failure in Promise.all

You fan out three LLM calls in Node.js. What does this code log?

async function call(id) {
  if (id === 2) throw new Error('rate limited')
  return id * 10
}
const out = []
try {
  const r = await Promise.all([1, 2, 3].map(call))
  out.push(...r)
} catch (e) {
  out.push(e.message)
}
console.log(out)
  1. A.[ 10, 'rate limited', 30 ]
  2. B.[ 10, 30 ]
  3. C.[ 10 ]
  4. D.[ 'rate limited' ]
Show answer

Answer: D. [ 'rate limited' ]

Promise.all rejects as soon as any promise rejects, so r is never assigned and the successful results 10 and 30 are lost; only the error message is pushed. For batch LLM calls, use Promise.allSettled or per-call try/catch with retries so one rate-limit error does not discard the others.

60. Missing the response you wait for

This test sometimes times out even though the request succeeds. Why, and what is the fix?

await page.getByRole('button', { name: 'Search' }).click()
const res = await page.waitForResponse('**/api/search*')
expect(res.ok()).toBeTruthy()
  1. A.waitForResponse only works with POST requests
  2. B.The glob pattern is invalid
  3. C.ok() should be status()
  4. D.The response can arrive before waitForResponse starts listening; start waiting before the click, for example const p = page.waitForResponse(...) then click, then await p
Show answer

Answer: D. The response can arrive before waitForResponse starts listening; start waiting before the click, for example const p = page.waitForResponse(...) then click, then await p

If the API is fast, the response finishes during the click and the listener registered afterwards never sees it. Creating the wait promise first, or using Promise.all with the click, removes the race.

Ready to test yourself?

Take a 15-question JavaScript MCQ test with a score at the end, or practise the full JavaScript question bank and earn points on the leaderboard.