Practice14 min read · Updated 2025-06-01

JavaScript Output Questions: 30 Tricky Examples That Test Real Understanding

These aren't trick questions — they're diagnostic tools. Each one exposes a specific gap in how JavaScript actually executes. Work through these before your next interview.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Output questions aren't about memorizing edge cases. They're about having a clear enough mental model of JavaScript's execution that you can trace code mentally, step by step. Each question below tests a specific concept. Read the question, think it through, then check the answer and explanation.

Section 1: Hoisting & Scope

Question 1

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

Output: undefined, then ReferenceError: Cannot access 'b' before initialization

var a is hoisted and initialized to undefined. let b is hoisted but sits in the Temporal Dead Zone — reading it throws. The error stops execution so b's log never prints a value.

---

Question 2

function outer() {
  var x = 10
  function inner() {
    console.log(x)
    var x = 20
    console.log(x)
  }
  inner()
}
outer()

Output: undefined, 20

There's a var x inside inner() — it's hoisted to the top of inner's scope and initialized to undefined. The first console.log(x) sees the local x (hoisted, undefined) — not the outer x = 10. Then x is assigned 20.

---

Question 3

console.log(typeof foo)
console.log(typeof bar)
function foo() {}
var bar = function() {}

Output: 'function', 'undefined'

Function declarations are fully hoisted — foo is a function before the console.log. bar is a var declaration, hoisted and initialized to undefined. The function expression hasn't been assigned yet.

---

Section 2: Closures & Loops

Question 4

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 3, 3, 3

All three callbacks close over the same var i. By the time any of them fire, the loop has completed and i is 3. There's only one i in memory.

---

Question 5

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

Output: 0, 1, 2

let is block-scoped and creates a new binding per iteration. Each callback closes over a different i. This is the fix to Question 4.

---

Question 6

const funcs = []
for (var i = 0; i < 3; i++) {
  funcs.push(function() { return i })
}
console.log(funcs[0](), funcs[1](), funcs[2]())

Output: 3 3 3

Same root cause as Question 4 — all three functions share one var i. Fix: let i, or funcs.push(((j) => () => j)(i)) to capture by IIFE.

---

Question 7

function makeAdder(x) {
  return function(y) {
    return x + y
  }
}
const add5  = makeAdder(5)
const add10 = makeAdder(10)
console.log(add5(3))
console.log(add10(3))
console.log(add5 === add10)

Output: 8, 13, false

Each makeAdder call creates a new closure with its own x. add5 and add10 are different function objects in memory.

---

Section 3: this Keyword

Question 8

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

Output: 'Alice', undefined (or error in strict mode)

obj.greet() — method call, this is obj. greet() — standalone call, this is undefined in strict mode or the global object in sloppy mode. In a browser without strict mode, this.name would be window.name (empty string).

---

Question 9

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

Output: undefined

Arrow functions don't have their own this. They inherit it from the lexical scope where they were defined. greet was defined at the top level (inside an object literal, which is not a scope), so this is the global object or undefined in strict mode.

---

Question 10

function Person(name) {
  this.name = name
}
Person.prototype.greet = function() {
  const inner = () => console.log(this.name)
  inner()
}

const alice = new Person('Alice') alice.greet()

Output: 'Alice'

The arrow function inner inherits this from greet's execution context. When greet is called as a method on alice, this is alice. The arrow correctly captures it.

---

Question 11

const obj = { x: 10 }
function logX() { console.log(this.x) }

logX.call(obj) logX.apply(obj) const bound = logX.bind(obj) bound() console.log(logX === bound)

Output: 10, 10, 10, false

call and apply invoke immediately with explicit this. bind returns a new function — logX and bound are different objects.

---

Section 4: Event Loop & Async

Question 12

console.log('start')
setTimeout(() => console.log('timeout'), 0)
Promise.resolve().then(() => console.log('promise'))
console.log('end')

Output: start, end, promise, timeout

Sync runs first. Then microtasks (Promise). Then macrotasks (setTimeout). Even with 0ms delay, setTimeout is a macrotask and always runs after all microtasks.

---

Question 13

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

Output: C, D, A, B

Both Promises queue as microtasks. All microtasks drain completely before any macrotask. So C, then D (chained microtask), then A, then B.

---

Question 14

async function main() {
  console.log('1')
  const result = await Promise.resolve('2')
  console.log(result)
  console.log('3')
}
console.log('before')
main()
console.log('after')

Output: before, 1, after, 2, 3

main() runs synchronously until await — logs 1, then suspends. Control returns to the caller. after logs. Then the microtask resumes: logs 2 and 3.

---

Question 15

const p = new Promise(resolve => {
  console.log('executor')
  resolve(1)
})
console.log('after new Promise')
p.then(v => console.log('then:', v))
console.log('end')

Output: executor, after new Promise, end, then: 1

The executor runs synchronously inside new Promise(). Resolution is immediate, but the .then() callback is always scheduled asynchronously as a microtask — it runs after the current synchronous code finishes.

---

Section 5: Type Coercion

Question 16

console.log(1 + '2')
console.log('3' - 1)
console.log(true + true)
console.log([] + [])
console.log({} + [])

Output: '12', 2, 2, '', '[object Object]'

+ with a string coerces to string concatenation. - has no string case — both sides convert to number. true coerces to 1. [] + [] — both become empty strings, concatenate to ''. {} + []{} becomes '[object Object]', [] becomes ''.

---

Question 17

console.log(0 == false)
console.log('' == false)
console.log(null == undefined)
console.log(null == false)
console.log(NaN == NaN)

Output: true, true, true, false, false

== coerces: false0, so 0 == 0. null == undefined is true by spec — the only things null loosely equals. null does NOT loosely equal false. NaN is never equal to anything, including itself.

---

Question 18

console.log(typeof null)
console.log(typeof undefined)
console.log(typeof NaN)
console.log(typeof function(){})
console.log(typeof [])

Output: 'object', 'undefined', 'number', 'function', 'object'

null'object' (historical bug). NaN is type number. Functions have their own typeof result. Arrays are objects.

---

Section 6: Prototypes & Classes

Question 19

function Animal(name) { this.name = name }
Animal.prototype.speak = function() { return this.name }

function Dog(name) { Animal.call(this, name) } Dog.prototype = Object.create(Animal.prototype) Dog.prototype.constructor = Dog

const d = new Dog('Rex') console.log(d.speak()) console.log(d instanceof Dog) console.log(d instanceof Animal) console.log(d.constructor === Dog)

Output: 'Rex', true, true, true

Classical inheritance: Dog.prototype is linked to Animal.prototype. instanceof walks the chain. The constructor reset is why d.constructor === Dog is true.

---

Question 20

class A {
  constructor() { this.x = 1 }
  getX() { return this.x }
}
class B extends A {
  constructor() {
    super()
    this.x = 2
  }
}
const b = new B()
console.log(b.getX())
console.log(b instanceof A)

Output: 2, true

super() runs A's constructor setting x = 1, then B's constructor sets x = 2. getX returns this.x which is 2 (the own property). instanceof checks the chain: B extends A so both are true.

---

Section 7: Objects & References

Question 21

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

Output: 2, true

b = a copies the reference, not the object. Both point to the same object in memory. Mutating through b mutates a. They're === equal because they're literally the same reference.

---

Question 22

const obj = { a: 1 }
Object.freeze(obj)
obj.a = 99
obj.b = 2
console.log(obj.a)
console.log(obj.b)

Output: 1, undefined

Freeze prevents mutation in strict mode (throws) or silently fails in sloppy mode. obj.a stays 1. obj.b was never added.

---

Question 23

const obj = {}
Object.defineProperty(obj, 'x', {
  value: 42,
  writable: false,
  enumerable: false,
})
console.log(obj.x)
console.log(Object.keys(obj))
obj.x = 100
console.log(obj.x)

Output: 42, [], 42

Non-enumerable means Object.keys doesn't see it. Non-writable means assignment is silently ignored (or throws in strict mode).

---

Section 8: Functions

Question 24

function foo(a, b = a * 2) {
  return a + b
}
console.log(foo(3))
console.log(foo(3, 4))

Output: 9, 7

Default parameter b = a * 2 — when b is omitted, b = 6, a + b = 9. When b is provided as 4, default is ignored: 3 + 4 = 7.

---

Question 25

const obj = {
  x: 10,
  getX() {
    return () => this.x
  }
}
const fn = obj.getX()
console.log(fn())

Output: 10

getX() is a method call — this is obj. The returned arrow function captures that this lexically. Calling fn() as a standalone function doesn't change this for the arrow — it still refers to obj.

---

Section 9: Advanced

Question 26

let x = 1
function outer() {
  let x = 2
  function inner() {
    let x = 3
    console.log(x)
  }
  inner()
  console.log(x)
}
outer()
console.log(x)

Output: 3, 2, 1

Each function has its own x. inner sees its own 3. outer sees its own 2. Global sees 1. The scope chain is read outward, never inward.

---

Question 27

async function fetchData() {
  return 42
}
const result = fetchData()
console.log(result)
result.then(v => console.log(v))

Output: Promise { 42 }, 42

async functions always return a Promise, even when returning a plain value. result is a Promise, not 42. .then() unwraps it.

---

Question 28

console.log([1,2,3].map(parseInt))

Output: [1, NaN, NaN]

map passes three arguments to its callback: (element, index, array). parseInt takes two: (string, radix). So the calls are parseInt(1, 0), parseInt(2, 1), parseInt(3, 2). Radix 0 is treated as 10 (returns 1). Radix 1 is invalid (returns NaN). '3' in base 2 has no digit '3' (returns NaN).

---

Question 29

const promise = new Promise((resolve) => {
  resolve(1)
  resolve(2)
  resolve(3)
})
promise.then(v => console.log(v))

Output: 1

A Promise settles exactly once. Only the first resolve call has any effect. All subsequent calls are silently ignored.

---

Question 30

function* gen() {
  yield 1
  yield 2
  return 3
}
const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

Output: { value: 1, done: false } { value: 2, done: false } { value: 3, done: true } { value: undefined, done: true }

yield pauses and returns the value with done: false. return ends the generator with done: true. Any call after completion returns { value: undefined, done: true }.

---

How to Use These

Work through each question before reading the answer. If you got it wrong, don't just accept the answer — trace the code manually using the execution model: what's on the call stack, what's in scope, what queues are active.

The questions you get wrong are exactly the concepts worth drilling. Every one of them maps to a topic with dedicated questions on JSPrep.

📚 Practice These Topics
Var let const
3–5 questions
Closure
8–12 questions
Scope
4–6 questions
Execution context
3–6 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read
Core Concepts
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences
9 min read
Interview Prep
Promise.all vs allSettled vs race vs any: The Complete Comparison
9 min read