Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/ad3637f31d386c7c42daf3de3fcf9d6b to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/ad3637f31d386c7c42daf3de3fcf9d6b to your computer and use it in GitHub Desktop.
🧠 Top 100 Tricky JavaScript Output Problems

🧠 Top 100 Tricky JavaScript Output Problems

Test your JavaScript knowledge with these output-based puzzles, organized by topic.
Each problem shows a code snippet β€” try to predict the output before revealing the answer!


πŸ“š Table of Contents

  1. Type Coercion & the + Operator β€” Q1–Q10
  2. Equality: == vs === β€” Q11–Q18
  3. var, let, const & Hoisting β€” Q19–Q28
  4. Closures & Scope β€” Q29–Q36
  5. Promises & Async/Await β€” Q37–Q46
  6. this Keyword β€” Q47–Q54
  7. Prototypes & Classes β€” Q55–Q62
  8. Arrays β€” Tricky Behaviors β€” Q63–Q70
  9. Objects β€” Tricky Behaviors β€” Q71–Q78
  10. Event Loop & setTimeout β€” Q79–Q86
  11. Spread, Rest & Destructuring β€” Q87–Q93
  12. Miscellaneous Mind-Benders β€” Q94–Q100

1. Type Coercion & the + Operator

JavaScript silently converts types. These questions expose exactly how and when.


Q1

console.log(1 + "2" + 3)
πŸ’‘ Answer
"123"

Why? Left-to-right evaluation: 1 + "2" β†’ "12" (string), then "12" + 3 β†’ "123".


Q2

console.log(1 + 2 + "3" + 4 + 5)
πŸ’‘ Answer
"3345"

Why? 1 + 2 β†’ 3 (both numbers), then 3 + "3" β†’ "33" (string from here), "33" + 4 β†’ "334", "334" + 5 β†’ "3345".


Q3

console.log(+"")
console.log(+" ")
console.log(+null)
console.log(+undefined)
console.log(+true)
console.log(+false)
πŸ’‘ Answer
0
0
0
NaN
1
0

Why? The unary + converts to Number. "" and " " coerce to 0, null β†’ 0, undefined β†’ NaN, true β†’ 1, false β†’ 0.


Q4

console.log([] + [])
console.log([] + {})
console.log({} + [])
πŸ’‘ Answer
""
"[object Object]"
0

Why?

  • [] + [] β†’ "" + "" β†’ ""
  • [] + {} β†’ "" + "[object Object]" β†’ "[object Object]"
  • {} + [] β€” when {} is at the start of a statement, JS parses it as an empty block, not an object. So it becomes +[] β†’ +"" β†’ 0.

Q5

console.log(true + true)
console.log(true + false)
console.log(false + false)
πŸ’‘ Answer
2
1
0

Why? Booleans coerce to numbers: true β†’ 1, false β†’ 0.


Q6

console.log("5" - 3)
console.log("5" * "3")
console.log("5" - true)
console.log("5" + - "3")
πŸ’‘ Answer
2
15
4
"5-3"

Why?

  • -, *, / always coerce to numbers. So "5" - 3 β†’ 2, "5" * "3" β†’ 15, "5" - true β†’ 5 - 1 β†’ 4.
  • "5" + - "3": unary - converts "3" to -3, so "5" + (-3) β†’ "5" + -3 β†’ "5-3" (string concat because of +).

Q7

console.log(null + 1)
console.log(undefined + 1)
console.log(null + undefined)
πŸ’‘ Answer
1
NaN
NaN

Why? null β†’ 0, undefined β†’ NaN. 0 + 1 β†’ 1, NaN + 1 β†’ NaN, 0 + NaN β†’ NaN.


Q8

console.log(0.1 + 0.2 === 0.3)
console.log(0.1 + 0.2)
πŸ’‘ Answer
false
0.30000000000000004

Why? Floating-point arithmetic in IEEE 754 binary representation causes precision loss. 0.1 + 0.2 is not exactly 0.3. Use Math.abs(a - b) < Number.EPSILON for comparisons.


Q9

console.log(+"3" + +"4")
console.log(+"3" + "4")
πŸ’‘ Answer
7
"34"

Why? +"3" β†’ 3 (number), +"4" β†’ 4 (number) β†’ 3 + 4 β†’ 7. But 3 + "4" β†’ "34" (string concat).


Q10

console.log([] == false)
console.log([] == 0)
console.log([] == "")
console.log([] == ![])
πŸ’‘ Answer
true
true
true
true

Why? [] converts to "" (via .toString()), then "" converts to 0. false β†’ 0. So all compare 0 == 0. For [] == ![]: ![] β†’ false β†’ 0, and [] β†’ 0, so 0 == 0 β†’ true.


2. Equality: == vs ===

Abstract equality (==) applies type coercion. Strict equality (===) does not.


Q11

console.log(null == undefined)
console.log(null === undefined)
console.log(null == 0)
console.log(null == false)
πŸ’‘ Answer
true
false
false
false

Why? null == undefined is a special rule in JS β€” they are equal only to each other with ==. null does not coerce to 0 or false with ==.


Q12

console.log(NaN == NaN)
console.log(NaN === NaN)
console.log(NaN != NaN)
πŸ’‘ Answer
false
false
true

Why? NaN is the only value in JS not equal to itself. Use Number.isNaN(val) to check.


Q13

console.log(0 == "0")
console.log(0 == "")
console.log("0" == "")
πŸ’‘ Answer
true
true
false

Why? 0 == "0": "0" coerces to 0 β†’ true. 0 == "": "" coerces to 0 β†’ true. "0" == "": both are strings, compared directly β†’ false (no coercion needed).


Q14

console.log(false == "false")
console.log(false == "0")
console.log(false == 0)
πŸ’‘ Answer
false
true
true

Why? false == "false": false β†’ 0, "false" β†’ NaN β†’ 0 == NaN β†’ false. false == "0": false β†’ 0, "0" β†’ 0 β†’ true. false == 0: false β†’ 0 β†’ true.


Q15

console.log(1 < 2 < 3)
console.log(3 > 2 > 1)
πŸ’‘ Answer
true
false

Why? Left-to-right: 1 < 2 β†’ true, then true < 3 β†’ 1 < 3 β†’ true. 3 > 2 β†’ true, then true > 1 β†’ 1 > 1 β†’ false.


Q16

console.log(typeof null)
console.log(null instanceof Object)
πŸ’‘ Answer
"object"
false

Why? typeof null === "object" is a historic bug in JS that was never fixed (it would break the web). But null instanceof Object correctly returns false since null has no prototype chain.


Q17

console.log(typeof NaN)
console.log(typeof undefined)
console.log(typeof function(){})
console.log(typeof class {})
πŸ’‘ Answer
"number"
"undefined"
"function"
"function"

Why? NaN is of type "number" (it's "Not a Number" but still a number type). Classes are syntactic sugar over functions, so typeof class {} β†’ "function".


Q18

console.log([] == ![])
console.log({} == !{})
πŸ’‘ Answer
true
false

Why? ![] β†’ false β†’ 0. [] β†’ "" β†’ 0. So 0 == 0 β†’ true. !{} β†’ false β†’ 0. {} β†’ "[object Object]" β†’ NaN. NaN == 0 β†’ false.


3. var, let, const & Hoisting

Hoisting moves declarations to the top of their scope. But only var is initialized with undefined.


Q19

console.log(a)
var a = 5
console.log(a)
πŸ’‘ Answer
undefined
5

Why? var declarations are hoisted and initialized to undefined. So by the time console.log(a) runs, a exists but has no value yet.


Q20

console.log(b)
let b = 5
πŸ’‘ Answer
ReferenceError: Cannot access 'b' before initialization

Why? let (and const) are hoisted but not initialized. Accessing them before their declaration is in the Temporal Dead Zone (TDZ).


Q21

var x = 1

function foo() {
  console.log(x)
  var x = 2
  console.log(x)
}

foo()
console.log(x)
πŸ’‘ Answer
undefined
2
1

Why? Inside foo, var x is hoisted to the top of the function scope. So the first console.log sees the local x (hoisted, undefined), not the global x = 1.


Q22

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
πŸ’‘ Answer
3
3
3

Why? var is function-scoped (not block-scoped). By the time the callbacks run, the loop is done and i === 3. All three closures share the same i.


Q23

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
πŸ’‘ Answer
0
1
2

Why? let is block-scoped. Each iteration creates a new binding of i, so each closure captures its own copy.


Q24

function test() {
  console.log(typeof foo)
  console.log(typeof bar)

  var foo = "hello"
  function bar() {}
}

test()
πŸ’‘ Answer
"undefined"
"function"

Why? var foo is hoisted but initialized as undefined. function bar() is fully hoisted β€” both its declaration AND its body.


Q25

const obj = { a: 1 }
obj.a = 2
obj.b = 3
console.log(obj)

obj = {}  // What happens?
πŸ’‘ Answer
{ a: 2, b: 3 }
TypeError: Assignment to constant variable.

Why? const prevents reassignment of the binding, but the object it points to is still mutable. You can change properties, but not the reference itself.


Q26

var a = 1
{
  var a = 2
  console.log(a)
}
console.log(a)
πŸ’‘ Answer
2
2

Why? var ignores blocks β€” it's function-scoped. The inner var a = 2 overwrites the outer a. Both logs see the same a.


Q27

let a = 1
{
  let a = 2
  console.log(a)
}
console.log(a)
πŸ’‘ Answer
2
1

Why? let is block-scoped. The inner a is a separate variable that only exists inside the {}. The outer a is untouched.


Q28

console.log(foo())
console.log(bar())

function foo() { return "foo" }
var bar = function() { return "bar" }
πŸ’‘ Answer
"foo"
TypeError: bar is not a function

Why? Function declarations are fully hoisted. bar is a var β€” hoisted as undefined, so calling undefined() throws a TypeError.


4. Closures & Scope

A closure is a function that remembers the variables from its outer scope even after that scope has exited.


Q29

function makeCounter() {
  let count = 0
  return function() {
    return ++count
  }
}

const counter1 = makeCounter()
const counter2 = makeCounter()

console.log(counter1())
console.log(counter1())
console.log(counter2())
console.log(counter1())
πŸ’‘ Answer
1
2
1
3

Why? Each call to makeCounter() creates a new closure with its own count. counter1 and counter2 are independent.


Q30

function outer() {
  let x = 10

  function inner() {
    let x = 20
    console.log(x)
  }

  inner()
  console.log(x)
}

outer()
πŸ’‘ Answer
20
10

Why? inner has its own x = 20 in its local scope. The outer x = 10 is unchanged β€” each function has its own scope.


Q31

function createFunctions() {
  const funcs = []
  for (var i = 0; i < 3; i++) {
    funcs.push(function() { return i })
  }
  return funcs
}

const fns = createFunctions()
console.log(fns[0]())
console.log(fns[1]())
console.log(fns[2]())
πŸ’‘ Answer
3
3
3

Why? Classic closure-in-loop trap. var i is shared across all iterations. By the time any function runs, i is 3.


Q32

// Fix for Q31 using IIFE
function createFunctions() {
  const funcs = []
  for (var i = 0; i < 3; i++) {
    funcs.push((function(j) {
      return function() { return j }
    })(i))
  }
  return funcs
}

const fns = createFunctions()
console.log(fns[0]())
console.log(fns[1]())
console.log(fns[2]())
πŸ’‘ Answer
0
1
2

Why? The IIFE immediately captures the current value of i as j. Each function closes over its own j.


Q33

let x = "global"

function foo() {
  console.log(x)
}

function bar() {
  let x = "local"
  foo()
}

bar()
πŸ’‘ Answer
"global"

Why? JavaScript uses lexical scoping (where the function is defined, not called). foo is defined in the global scope, so it sees the global x, not bar's local x.


Q34

function a() {
  let n = 0
  function b() { n++ }
  function c() { console.log(n) }
  b(); b(); b()
  c()
}

a()
πŸ’‘ Answer
3

Why? b and c both close over the same n. Calling b() three times increments the shared n to 3.


Q35

const add = (function() {
  let total = 0
  return function(n) {
    total += n
    return total
  }
})()

console.log(add(5))
console.log(add(3))
console.log(add(2))
πŸ’‘ Answer
5
8
10

Why? The IIFE creates a private total that persists across calls via closure. This is the module pattern.


Q36

function foo(x) {
  return function(y) {
    return function(z) {
      return x + y + z
    }
  }
}

console.log(foo(1)(2)(3))
πŸ’‘ Answer
6

Why? Currying β€” each function closes over the previous arguments. x=1, y=2, z=3 β†’ 1+2+3 β†’ 6.


5. Promises & Async/Await

Async code runs in the event loop. Understanding microtasks vs macrotasks is key.


Q37

console.log("1")

setTimeout(() => console.log("2"), 0)

Promise.resolve().then(() => console.log("3"))

console.log("4")
πŸ’‘ Answer
1
4
3
2

Why? Synchronous code runs first (1, 4). Then microtasks (Promise .then) run before macrotasks (setTimeout). So 3 before 2.


Q38

Promise.resolve(1)
  .then(x => x + 1)
  .then(x => { throw new Error("oops") })
  .catch(e => e.message)
  .then(x => console.log(x))
πŸ’‘ Answer
"oops"

Why? The error thrown in .then is caught by .catch. .catch returns the error message "oops", and the final .then logs it.


Q39

async function foo() {
  return 1
}

foo().then(console.log)
πŸ’‘ Answer
1

Why? An async function always returns a Promise. return 1 is equivalent to Promise.resolve(1).


Q40

async function foo() {
  console.log("A")
  await Promise.resolve()
  console.log("B")
}

console.log("C")
foo()
console.log("D")
πŸ’‘ Answer
C
A
D
B

Why? C runs first (sync). foo() starts: logs A, then hits await β€” suspends and returns control. D logs (sync). Then the microtask queue runs: B logs.


Q41

async function foo() {
  const p = new Promise((resolve) => {
    setTimeout(resolve, 100)
  })
  await p
  return "done"
}

foo().then(console.log)
console.log("sync")
πŸ’‘ Answer
"sync"
"done"

Why? foo() awaits a 100ms timer. "sync" logs immediately. After 100ms, "done" is logged.


Q42

async function fail() {
  throw new Error("async error")
}

fail()
  .catch(e => console.log("caught:", e.message))
πŸ’‘ Answer
caught: async error

Why? throw inside an async function causes the returned promise to reject. .catch handles it.


Q43

Promise.all([
  Promise.resolve(1),
  Promise.reject("error"),
  Promise.resolve(3),
]).then(console.log).catch(console.error)
πŸ’‘ Answer
"error"

Why? Promise.all fails fast β€” if any promise rejects, the whole thing rejects with that reason. The resolved values are discarded.


Q44

async function fetchAll() {
  const a = await Promise.resolve("A")
  const b = await Promise.resolve("B")
  const c = await Promise.resolve("C")
  return [a, b, c]
}

fetchAll().then(console.log)
πŸ’‘ Answer
["A", "B", "C"]

Why? Each await resolves sequentially. The final return resolves the async function's promise with the array.


Q45

const p = new Promise((resolve) => {
  resolve(1)
  resolve(2)
  resolve(3)
})

p.then(console.log)
πŸ’‘ Answer
1

Why? A Promise can only settle once. The first resolve(1) locks it in. The subsequent resolves are ignored.


Q46

async function main() {
  try {
    const result = await Promise.reject(new Error("fail"))
  } catch (e) {
    console.log("caught:", e.message)
  } finally {
    console.log("finally")
  }
}

main()
πŸ’‘ Answer
caught: fail
finally

Why? await unwraps the rejected promise and throws the error, which is caught by try/catch. finally always runs.


6. this Keyword

this depends on how a function is called, not where it's defined β€” except in arrow functions.


Q47

const obj = {
  name: "Alice",
  greet: function() {
    console.log(this.name)
  }
}

obj.greet()

const fn = obj.greet
fn()
πŸ’‘ Answer
"Alice"
undefined

Why? obj.greet() β€” this is obj. fn() β€” called without context, this is undefined (strict mode) or globalThis (sloppy mode), which has no name.


Q48

const obj = {
  name: "Bob",
  greet: () => {
    console.log(this.name)
  }
}

obj.greet()
πŸ’‘ Answer
undefined

Why? Arrow functions don't have their own this. They inherit this from the enclosing lexical scope. Here that's the module/global scope, which has no name.


Q49

function Person(name) {
  this.name = name
  this.sayHi = function() {
    console.log("Hi, I'm " + this.name)
  }
  this.sayHiArrow = () => {
    console.log("Hi, I'm " + this.name)
  }
}

const p = new Person("Carol")
const hi = p.sayHi
const hiArrow = p.sayHiArrow

hi()
hiArrow()
πŸ’‘ Answer
"Hi, I'm undefined"
"Hi, I'm Carol"

Why? hi() loses context β€” this is global/undefined. hiArrow is an arrow function capturing this from the constructor (the Person instance), so it always knows this.name.


Q50

const obj = {
  x: 10,
  getX: function() {
    const inner = function() {
      return this.x
    }
    return inner()
  }
}

console.log(obj.getX())
πŸ’‘ Answer
undefined

Why? inner() is a regular function called without context. Inside inner, this is globalThis (or undefined in strict mode), not obj. Fix: use arrow function or const self = this.


Q51

const obj = {
  x: 10,
  getX: function() {
    const inner = () => this.x
    return inner()
  }
}

console.log(obj.getX())
πŸ’‘ Answer
10

Why? Arrow function inherits this from getX, where this is obj. So this.x β†’ 10.


Q52

function greet() {
  console.log(this.name)
}

const user = { name: "Dave" }

const bound = greet.bind(user)
bound()
bound.call({ name: "Eve" })
πŸ’‘ Answer
"Dave"
"Dave"

Why? .bind() permanently locks this to user. Even calling .call() with a different context cannot override a bound function's this.


Q53

class Timer {
  constructor() {
    this.seconds = 0
  }

  start() {
    setInterval(function() {
      this.seconds++
      console.log(this.seconds)
    }, 1000)
  }
}

new Timer().start()
πŸ’‘ Answer
NaN  (repeatedly)

Why? The callback passed to setInterval is a regular function. this inside it is globalThis (not the Timer instance). globalThis.seconds is undefined, and undefined++ is NaN.


Q54

const obj = { val: 42 }

function logVal() {
  console.log(this.val)
}

logVal.call(obj)
logVal.apply(obj)
logVal.bind(obj)()
πŸ’‘ Answer
42
42
42

Why? call, apply, and bind all set this to obj. call/apply invoke immediately. bind returns a new function that you must call ().


7. Prototypes & Classes


Q55

function Animal(name) {
  this.name = name
}

Animal.prototype.speak = function() {
  return `${this.name} makes a sound`
}

const dog = new Animal("Rex")
console.log(dog.speak())
console.log(dog.hasOwnProperty("name"))
console.log(dog.hasOwnProperty("speak"))
πŸ’‘ Answer
"Rex makes a sound"
true
false

Why? name is set directly on the instance (own property). speak is on the prototype, not the instance.


Q56

class A {
  constructor() {
    this.x = 1
  }
}

class B extends A {
  constructor() {
    super()
    this.y = 2
  }
}

const b = new B()
console.log(b.x, b.y)
console.log(b instanceof B)
console.log(b instanceof A)
πŸ’‘ Answer
1 2
true
true

Why? super() calls the parent constructor, setting this.x = 1. instanceof checks the prototype chain β€” B extends A, so b is an instance of both.


Q57

class Counter {
  #count = 0

  increment() { this.#count++ }
  get value() { return this.#count }
}

const c = new Counter()
c.increment()
c.increment()
console.log(c.value)
console.log(c.#count)
πŸ’‘ Answer
2
SyntaxError: Private field '#count' must be declared in an enclosing class

Why? #count is a private class field β€” accessible only inside the class body. Trying to access it from outside throws a SyntaxError.


Q58

function Foo() {}
const f = new Foo()

console.log(f.__proto__ === Foo.prototype)
console.log(Foo.prototype.constructor === Foo)
console.log(f.constructor === Foo)
πŸ’‘ Answer
true
true
true

Why? f.__proto__ points to Foo.prototype. Every prototype has a constructor property pointing back to its constructor function.


Q59

class Animal {
  constructor(name) {
    this.name = name
  }

  speak() {
    return `${this.name} speaks`
  }
}

class Dog extends Animal {
  speak() {
    return super.speak() + " (woof!)"
  }
}

const d = new Dog("Rex")
console.log(d.speak())
πŸ’‘ Answer
"Rex speaks (woof!)"

Why? super.speak() calls the parent's speak method. The result is concatenated.


Q60

const obj = Object.create(null)
console.log(obj.toString)
console.log(typeof obj)
πŸ’‘ Answer
undefined
"object"

Why? Object.create(null) creates an object with no prototype β€” it has none of the built-in methods like toString, hasOwnProperty, etc.


Q61

class MyClass {
  static count = 0

  constructor() {
    MyClass.count++
  }
}

new MyClass()
new MyClass()
new MyClass()
console.log(MyClass.count)
πŸ’‘ Answer
3

Why? static count belongs to the class itself, not instances. Each new MyClass() increments the shared MyClass.count.


Q62

const proto = {
  greet() { return `Hi, I'm ${this.name}` }
}

const obj = Object.create(proto)
obj.name = "Frank"

console.log(obj.greet())
console.log(obj.hasOwnProperty("greet"))
console.log(obj.hasOwnProperty("name"))
πŸ’‘ Answer
"Hi, I'm Frank"
false
true

Why? greet is on the prototype, not obj itself. name was directly set on obj.


8. Arrays β€” Tricky Behaviors


Q63

console.log([1, 2, 3].map(parseInt))
πŸ’‘ Answer
[1, NaN, NaN]

Why? map passes three args: (element, index, array). parseInt takes (string, radix). So: parseInt(1, 0) β†’ 1 (radix 0 treated as 10), parseInt(2, 1) β†’ NaN (radix 1 invalid), parseInt(3, 2) β†’ NaN (3 is not a valid base-2 number).


Q64

const arr = [1, 2, 3]
arr[10] = 11
console.log(arr.length)
console.log(arr[5])
πŸ’‘ Answer
11
undefined

Why? Setting index 10 creates a sparse array with length of 11. The slots in between ([3] to [9]) are empty holes, returning undefined when accessed.


Q65

const a = [1, 2, 3]
const b = [1, 2, 3]

console.log(a == b)
console.log(a === b)
console.log(a.toString() === b.toString())
πŸ’‘ Answer
false
false
true

Why? Arrays are objects β€” compared by reference, not value. a and b are different objects in memory. But .toString() converts both to "1,2,3" β€” same string.


Q66

console.log([1, [2, [3]]].flat())
console.log([1, [2, [3]]].flat(Infinity))
πŸ’‘ Answer
[1, 2, [3]]
[1, 2, 3]

Why? .flat() with no argument defaults to depth 1. .flat(Infinity) flattens all levels.


Q67

const arr = [3, 1, 2, 10, 20]
console.log(arr.sort())
πŸ’‘ Answer
[1, 10, 2, 20, 3]

Why? .sort() with no comparator converts elements to strings and sorts lexicographically. "10" < "2" because "1" < "2". Always use arr.sort((a, b) => a - b) for numbers.


Q68

const arr = [1, 2, 3, 4, 5]

const result = arr.reduce((acc, curr) => {
  if (curr % 2 === 0) acc.push(curr * 2)
  return acc
}, [])

console.log(result)
πŸ’‘ Answer
[4, 8]

Why? Only even numbers (2, 4) pass the condition. Each is doubled. Equivalent to .filter().map() in one pass.


Q69

const arr = [1, 2, 3]
arr.forEach((item, i) => {
  if (item === 2) arr.splice(i, 1)
})
console.log(arr)
πŸ’‘ Answer
[1, 3]

But β€” mutating an array during forEach is dangerous. If you splice element at index 1, the remaining items shift, and the iterator can skip elements depending on the case.


Q70

console.log(typeof [])
console.log(Array.isArray([]))
console.log([] instanceof Array)
πŸ’‘ Answer
"object"
true
true

Why? typeof [] returns "object" (arrays are objects). Use Array.isArray() for reliable array detection, especially across iframes.


9. Objects β€” Tricky Behaviors


Q71

const key = "name"
const obj = { [key]: "Alice" }
console.log(obj.name)
console.log(obj[key])
console.log(obj["name"])
πŸ’‘ Answer
"Alice"
"Alice"
"Alice"

Why? Computed property names [key] use the variable's value as the key. All three accesses are equivalent.


Q72

const obj = { a: 1, b: 2, c: 3 }
const { a, ...rest } = obj
console.log(a)
console.log(rest)
πŸ’‘ Answer
1
{ b: 2, c: 3 }

Why? Rest syntax in destructuring collects all remaining own enumerable properties into a new object.


Q73

const a = { x: 1 }
const b = { ...a }
b.x = 99

console.log(a.x)
console.log(b.x)
πŸ’‘ Answer
1
99

Why? Spread creates a shallow copy. Primitive values are copied by value. Changing b.x doesn't affect a.


Q74

const a = { nested: { val: 1 } }
const b = { ...a }
b.nested.val = 99

console.log(a.nested.val)
πŸ’‘ Answer
99

Why? Spread is shallow. b.nested points to the same object as a.nested. Mutating it affects both.


Q75

const obj = {}
obj[true] = "boolean"
obj[1] = "number"
obj["1"] = "string"

console.log(Object.keys(obj))
console.log(obj[1])
πŸ’‘ Answer
["true", "1"]
"string"

Why? Object keys are always strings (or Symbols). true β†’ "true". 1 and "1" are the same key β€” the last write ("string") wins.


Q76

const obj = { a: 1 }
Object.freeze(obj)

obj.a = 999
obj.b = 2

console.log(obj.a)
console.log(obj.b)
πŸ’‘ Answer
1
undefined

Why? Object.freeze() prevents adding, deleting, or modifying properties. In non-strict mode, these operations silently fail.


Q77

function Person(name) {
  this.name = name
}

const p = new Person("Grace")
console.log(p.name)

// What if we forget `new`?
const q = Person("Henry")
console.log(q)
console.log(globalThis.name)  // in browser: window.name
πŸ’‘ Answer
"Grace"
undefined
"Henry"

Why? Without new, Person runs as a regular function. this is globalThis, so name is set globally. The function returns undefined by default.


Q78

const obj = {
  0: "zero",
  1: "one",
  length: 2
}

console.log(Array.from(obj))
console.log([...obj])
πŸ’‘ Answer
["zero", "one"]
TypeError: obj is not iterable

Why? Array.from() works on array-like objects (has length + numeric keys). But spread [...] requires the object to be iterable (have [Symbol.iterator]). Plain objects are not iterable.


10. Event Loop & setTimeout


Q79

console.log("start")

setTimeout(() => console.log("timeout 1"), 0)
setTimeout(() => console.log("timeout 2"), 0)

Promise.resolve().then(() => console.log("promise 1"))
Promise.resolve().then(() => console.log("promise 2"))

console.log("end")
πŸ’‘ Answer
start
end
promise 1
promise 2
timeout 1
timeout 2

Why? Order: synchronous β†’ microtasks (Promises) β†’ macrotasks (setTimeout). Both promises resolve before either timeout callback runs.


Q80

setTimeout(() => console.log("A"), 0)
setTimeout(() => console.log("B"), 100)
setTimeout(() => console.log("C"), 0)
πŸ’‘ Answer
A
C
B

Why? A and C both have 0ms delay β€” they run in order of registration. B runs after ~100ms.


Q81

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), i * 100)
}
πŸ’‘ Answer
3
3
3

Why? var i is shared. All three callbacks run after the loop completes, reading the final i = 3.


Q82

console.log("A")

setTimeout(function() {
  console.log("B")
  Promise.resolve().then(() => console.log("C"))
}, 0)

Promise.resolve().then(function() {
  console.log("D")
  setTimeout(() => console.log("E"), 0)
})

console.log("F")
πŸ’‘ Answer
A
F
D
B
C
E

Why?

  1. Sync: A, F
  2. Microtasks: D (queues E as a macrotask)
  3. Macrotasks: B (queues C as microtask), then drain microtasks β†’ C, then next macrotask β†’ E

Q83

async function main() {
  console.log("1")
  await null
  console.log("2")
  await null
  console.log("3")
}

console.log("before")
main()
console.log("after")
πŸ’‘ Answer
before
1
after
2
3

Why? main() starts synchronously (logs 1). Each await yields to the microtask queue. "after" logs before the awaited continuations.


Q84

const p = new Promise((resolve) => {
  console.log("executor")
  resolve("done")
  console.log("after resolve")
})

p.then(console.log)
console.log("sync")
πŸ’‘ Answer
executor
after resolve
sync
done

Why? The Promise executor runs synchronously (immediately). "after resolve" runs even after resolve() is called. The .then callback is a microtask β€” runs after all sync code.


Q85

setTimeout(() => console.log("macro"), 0)

queueMicrotask(() => console.log("micro 1"))

Promise.resolve().then(() => {
  console.log("micro 2")
  queueMicrotask(() => console.log("micro 3"))
})

queueMicrotask(() => console.log("micro 4"))
πŸ’‘ Answer
micro 1
micro 2
micro 4
micro 3
macro

Why? All microtasks (queueMicrotask and Promise .then) run before macrotasks. micro 3 is queued during microtask processing β€” it still runs before the macrotask.


Q86

function delay(ms) {
  return new Promise(res => setTimeout(res, ms))
}

async function run() {
  console.log("start")
  await delay(100)
  console.log("after 100ms")
  await delay(200)
  console.log("after 300ms total")
}

run()
πŸ’‘ Answer
start
(100ms later) after 100ms
(200ms later) after 300ms total

Why? Each await delay(ms) pauses execution for that duration. Total time β‰ˆ 300ms. This is the correct way to use sequential async delays.


11. Spread, Rest & Destructuring


Q87

function sum(...args) {
  return args.reduce((a, b) => a + b, 0)
}

console.log(sum(1, 2, 3))
console.log(sum(...[1, 2, 3]))
console.log(sum(...[1, 2], 3))
πŸ’‘ Answer
6
6
6

Why? Rest (...args) collects all arguments into an array. Spread (...[...]) expands an array into individual arguments. All three calls pass 1, 2, 3.


Q88

const [a, , b, c = 99] = [1, 2, 3]
console.log(a, b, c)
πŸ’‘ Answer
1 3 99

Why? The second element is skipped (, ,). b gets the third element 3. c has a default of 99 β€” since the array has no 4th element, the default is used.


Q89

const obj = { a: 1, b: { c: 2 } }
const { a, b: { c } } = obj

console.log(a)
console.log(c)
console.log(b)
πŸ’‘ Answer
1
2
ReferenceError: b is not defined

Why? b: { c } is nested destructuring β€” b is used as a pattern, not bound as a variable. Only c is extracted. a and c exist; b does not.


Q90

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]

const combined = [...arr1, ...arr2]
console.log(combined)

const [first, ...rest] = combined
console.log(first, rest)
πŸ’‘ Answer
[1, 2, 3, 4, 5, 6]
1 [2, 3, 4, 5, 6]

Why? Spread combines arrays. Rest in destructuring captures everything after the first element.


Q91

function greet({ name = "World", greeting = "Hello" } = {}) {
  return `${greeting}, ${name}!`
}

console.log(greet({ name: "Alice" }))
console.log(greet())
console.log(greet({ greeting: "Hey", name: "Bob" }))
πŸ’‘ Answer
"Hello, Alice!"
"Hello, World!"
"Hey, Bob!"

Why? Destructured parameters with defaults. The = {} default allows calling with no argument at all.


Q92

let a = 1, b = 2
;[a, b] = [b, a]
console.log(a, b)
πŸ’‘ Answer
2 1

Why? Destructuring swap β€” no temp variable needed. The right side [b, a] creates a new array [2, 1], then destructures back into a and b.


Q93

const matrix = [[1, 2], [3, 4], [5, 6]]
const [[a, b], [c], [, f]] = matrix

console.log(a, b, c, f)
πŸ’‘ Answer
1 2 3 6

Why? Nested array destructuring. [c] only captures the first element of [3, 4]. [, f] skips first and takes second (6).


12. Miscellaneous Mind-Benders


Q94

console.log(typeof typeof 42)
πŸ’‘ Answer
"string"

Why? typeof 42 β†’ "number" (a string). typeof "number" β†’ "string".


Q95

console.log(0.1 + 0.2 == 0.3)
console.log(9007199254740992 === 9007199254740993)
πŸ’‘ Answer
false
true

Why? Floating-point precision. And 9007199254740992 is Number.MAX_SAFE_INTEGER. Beyond this, integers can't be precisely represented β€” both values map to the same float.


Q96

console.log(!!"")
console.log(!!0)
console.log(!!" ")
console.log(!!"false")
console.log(!!null)
console.log(!![])
console.log(!!{})
πŸ’‘ Answer
false
false
true
true
false
true
true

Why? Falsy values: "", 0, null, undefined, NaN, false. Everything else is truthy β€” including " " (non-empty), "false" (non-empty string), [], {}.


Q97

const fn = (a = b, b = 1) => [a, b]

console.log(fn(undefined, 2))
console.log(fn(undefined, undefined))
πŸ’‘ Answer
[2, 2]
ReferenceError: Cannot access 'b' before initialization

Why? Default parameters are evaluated left to right. In fn(undefined, 2), a defaults to b which is 2. In fn(), a defaults to b, but b hasn't been initialized yet β€” TDZ error.


Q98

console.log(1..toString())
console.log((1).toString())
console.log(1 .toString())
πŸ’‘ Answer
"1"
"1"
"1"

Why? You can't write 1.toString() directly β€” the . is parsed as the decimal point. 1..toString() uses the second dot for method access. Parens or a space also resolve the ambiguity.


Q99

function* gen() {
  yield 1
  yield 2
  yield 3
}

const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
πŸ’‘ Answer
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Why? Generators pause at each yield. .next() resumes until the next yield or return. After the last yield, done: true and value: undefined.


Q100

const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === "number") return 42
    if (hint === "string") return "hello"
    return true
  }
}

console.log(+obj)
console.log(`${obj}`)
console.log(obj + "")
πŸ’‘ Answer
42
"hello"
"true"

Why? [Symbol.toPrimitive] lets you control type conversion. +obj (unary) β†’ hint "number" β†’ 42. Template literal β†’ hint "string" β†’ "hello". obj + "" β†’ hint "default" β†’ true, then "true" + "" β†’ "true".


🎯 Final Tips

Trap Always Remember
+ with strings Any string β†’ concatenation
== coercion Use === unless you know why you need ==
var hoisting Use let/const exclusively in modern JS
Closure in loops let creates per-iteration scope; var does not
this in callbacks Use arrow functions or .bind()
Promise.all Fails fast β€” use allSettled for resilience
Array .sort() Always pass a comparator for numbers
Shallow copy Spread and Object.assign are shallow
Microtasks vs macrotasks Promises before setTimeout, always
Floating point Never compare floats with === directly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment