All interview guides

JavaScript Interview Questions and Answers

24 questions that come up in JavaScript technical interviews, each with the answer and an explanation of why it is right.

Topics covered: event loop, this binding, scope, types, closures, arrays, async, collections, coercion, objects, variables, operators, prototypes.

Test yourself — 106 question bank

1. What does this print?

Expert
js
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')

Answer: 1 4 3 2

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.

Official documentation →

2. What does this print?

Advanced
js
const obj = {
  name: 'outer',
  greet() {
    const inner = () => this.name
    return inner()
  }
}
console.log(obj.greet())

Answer: 'outer'

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.

Official documentation →

3. What does this print?

Intermediate
js
console.log(a)
console.log(b)
var a = 1
let b = 2

Answer: undefined, then a ReferenceError

`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`.

Official documentation →

4. What does this print?

Beginner
js
let x = 5
x = x + '5'
console.log(x, typeof x)

Answer: 55 string

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.

Official documentation →

5. What does this print?

Advanced
js
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.

Official documentation →

6. What does this print?

Intermediate
js
const arr = [1, 2, 3]
const doubled = arr.map(n => n * 2)
const filtered = arr.filter(n => n > 1)
console.log(arr.length, doubled, filtered)

Answer: 3 [2, 4, 6] [2, 3]

`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.

Official documentation →

7. What does this print?

Beginner
js
const arr = [1, 2, 3]
arr.push(4)
console.log(arr.length, arr[0], arr[10])

Answer: 4 1 undefined

`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.

Official documentation →

8. What does this print?

Expert
js
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.

Official documentation →

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.

Official documentation →

10. This function should return the total of all prices. Which line causes it to return a Promise instead of a number?

Expert
js
1  async function total(ids) {
2    const items = await Promise.all(ids.map(fetchItem))
3    const sum = items.reduce(async (acc, item) => {
4      return (await acc) + item.price
5    }, 0)
6    return sum
7  }

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)`.

Official documentation →

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.

Official documentation →

12. This should log each id after fetching, in order. Instead it logs nothing useful. Which line is wrong?

Intermediate
js
1  ids.forEach(async (id) => {
2    const user = await getUser(id)
3    console.log(user.name)
4  })
5  console.log('done')

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.

Official documentation →

13. Fill in the blank so that `unique` contains each value only once.

Expert
js
const values = [1, 2, 2, 3, 3, 3]
const unique = [...new ____(values)]

Answer: Set

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.

Official documentation →

14. What does this print?

Advanced
js
const arr = [1, 2, 3]
arr.length = 0
console.log(arr[0], arr.length)

Answer: undefined 0

`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.

Official documentation →

15. What does this print?

Intermediate
js
console.log(1 == '1', 1 === '1', null == undefined, null === undefined)

Answer: true false true false

`==` 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.

Official documentation →

16. What does this print?

Beginner
js
const person = { name: 'Sam', age: 30 }
console.log(person.name, person['age'], person.email)

Answer: Sam 30 undefined

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.

Official documentation →

17. What does this print?

Intermediate
js
const person = { name: 'Sam', address: { city: 'Cairo' } }
const copy = { ...person }
copy.name = 'Alex'
copy.address.city = 'Giza'
console.log(person.name, person.address.city)

Answer: Sam Giza

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.

Official documentation →

18. What does this print?

Beginner
js
console.log(typeof 'hi', typeof 42, typeof true, typeof undefined)

Answer: string number boolean undefined

`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.

Official documentation →

19. What does this print?

Advanced
js
console.log([1, 2, 3] + [4, 5])

Answer: "1,2,34,5"

`+` 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.

Official documentation →

20. Put these in the order they are logged.

Expert
js
console.log('A')
queueMicrotask(() => console.log('B'))
setTimeout(() => console.log('C'), 0)
Promise.resolve().then(() => console.log('D'))

Answer: A

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.

Official documentation →

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.

Official documentation →

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`.

Official documentation →

23. What does this print?

Advanced
js
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()`.

Official documentation →

24. What does this print?

Intermediate
js
const nums = [10, 9, 1, 2]
console.log(nums.sort())

Answer: [1, 10, 2, 9]

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.

Official documentation →

Ready to test yourself?

The full JavaScript bank has 106 questions across 4 difficulty levels — timed, shuffled, and scored.

Take the JavaScript quiz