pencils ready ✎

JavaScript MCQs multiple-choice questions with answers & explanations

All 42 JavaScript quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

  1. 1.

    What does typeof null return?

    easy
    1. A'null'
    2. B'undefined'
    3. C'object'
    4. D'NaN'
    Show answer

    Answer: C ('object')

    typeof null is 'object', a bug from the first JavaScript engine that was kept for backward compatibility. To test for null, compare directly with value === null.

  2. 2.

    What does this log?

    easy
    console.log(1 + '2' - 1);
    1. A11
    2. B2
    3. C'111'
    4. DNaN
    Show answer

    Answer: A (11)

    Evaluated left to right: 1 + '2' concatenates to '12' because one operand is a string. - has no string meaning, so '12' - 1 converts to numbers and gives 11.

  3. 3.

    What does this log?

    easy
    const count = 0;
    console.log(count || 10, count ?? 10);
    1. A0 0
    2. B10 10
    3. C10 0
    4. D0 10
    Show answer

    Answer: C (10 0)

    || falls back on any falsy value, and 0 is falsy, so it gives 10. ?? only falls back on null or undefined, so it keeps 0. Use ?? for defaults when 0 or an empty string are valid values.

  4. 4.

    Which of these expressions evaluates to false?

    mid
    1. Anull == undefined
    2. B'' == 0
    3. C'0' == false
    4. Dnull == 0
    5. E[] == false
    Show answer

    Answer: D (null == 0)

    With ==, null is only loosely equal to undefined (and itself). It is never converted to a number, so null == 0 is false. The others coerce: '' == 0 compares 0 == 0, '0' == false becomes 0 == 0, and [] == false becomes '' == 0.

  5. 5.

    What does this log?

    mid
    const values = [NaN];
    console.log(values.indexOf(NaN), values.includes(NaN));
    1. A0 true
    2. B-1 false
    3. C-1 true
    4. D0 false
    Show answer

    Answer: C (-1 true)

    indexOf uses strict equality, and NaN === NaN is false, so it cannot find NaN and returns -1. includes uses the SameValueZero algorithm, which treats NaN as equal to itself, so it returns true.

  6. 6.

    What does [] == ![] evaluate to?

    hard
    1. Atrue
    2. Bfalse
    3. CIt throws a TypeError
    Show answer

    Answer: A (true)

    ![] is false, because every object is truthy. Then [] == false: the boolean becomes 0, the array goes through ToPrimitive to '', and '' becomes 0. So it is 0 == 0, which is true.

  7. 7.

    What does this log?

    easy
    function test() {
      if (true) {
        var x = 1;
        let y = 2;
      }
      console.log(typeof x, typeof y);
    }
    test();
    1. Anumber number
    2. Bnumber undefined
    3. Cundefined undefined
    4. DIt throws a ReferenceError
    Show answer

    Answer: B (number undefined)

    var is function-scoped, so x is visible after the if block. let is block-scoped, so no y exists at the console.log. typeof on an undeclared name returns 'undefined' instead of throwing.

  8. 8.

    What does this log?

    easy
    console.log(typeof declared, typeof expressed);
    
    function declared() {}
    var expressed = function () {};
    1. Afunction function
    2. Bundefined undefined
    3. CIt throws a ReferenceError
    4. Dfunction undefined
    Show answer

    Answer: D (function undefined)

    Function declarations are hoisted along with their body, so declared is already a function. For var expressed = function () {}, only the var declaration is hoisted and initialised to undefined; the function is assigned when that line runs.

  9. 9.

    What happens when this runs?

    mid
    let x = 'outer';
    {
      console.log(x);
      let x = 'inner';
    }
    1. AIt logs 'outer'
    2. BIt logs undefined
    3. CIt throws a ReferenceError
    4. DIt logs 'inner'
    Show answer

    Answer: C (It throws a ReferenceError)

    The inner let x is hoisted to the top of the block, so it shadows the outer x for the whole block. Before its declaration line runs, it is in the temporal dead zone, and reading it throws ReferenceError: Cannot access 'x' before initialization.

  10. 10.

    What happens when this runs?

    hard
    console.log(typeof notDeclared);
    console.log(typeof later);
    let later = 1;
    1. Aundefined, then undefined
    2. Bundefined, then number
    3. Cundefined, then a ReferenceError
    4. DA ReferenceError on the first line
    Show answer

    Answer: C (undefined, then a ReferenceError)

    typeof is safe on names that are not declared anywhere, so the first line logs 'undefined'. later is declared with let in this scope, so it exists but is in the temporal dead zone, and even typeof throws a ReferenceError there.

  11. 11.

    What does this code log?

    mid
    for (var i = 0; i < 3; i++) {
      setTimeout(() => console.log(i), 0);
    }
    1. A0 1 2
    2. B3 3 3
    3. Cundefined three times
    4. D0 0 0
    Show answer

    Answer: B (3 3 3)

    var is function-scoped, so all three callbacks close over the same i, which is 3 by the time they run. Use let to get a fresh binding per iteration.

  12. 12.

    What does this log?

    easy
    function makeCounter() {
      let count = 0;
      return () => ++count;
    }
    const a = makeCounter();
    const b = makeCounter();
    a();
    a();
    console.log(a(), b());
    1. A3 1
    2. B3 4
    3. C1 1
    4. D2 1
    Show answer

    Answer: A (3 1)

    Each call to makeCounter creates a new count variable, and the returned function closes over its own copy. a has been called twice already, so the third call returns 3, while b starts fresh and returns 1.

  13. 13.

    What does this log?

    mid
    const obj = {
      value: 42,
      getArrow() {
        return () => this.value;
      },
    };
    const fn = obj.getArrow();
    const other = { value: 7, fn };
    console.log(fn(), other.fn());
    1. A42 7
    2. Bundefined 7
    3. CIt throws a TypeError
    4. D42 42
    Show answer

    Answer: D (42 42)

    An arrow function has no this of its own. It captures this from where it was created, here the getArrow call where this was obj. Calling it later as other.fn() cannot rebind it, so both calls return 42.

  14. 14.

    What does this log?

    mid
    function show() {
      return this.x;
    }
    const a = { x: 1 };
    const b = { x: 2 };
    const bound = show.bind(a);
    console.log(bound.call(b), bound.bind(b)());
    1. A2 2
    2. B1 2
    3. C1 1
    4. D2 1
    Show answer

    Answer: C (1 1)

    bind returns a function whose this is permanently fixed. Later call, apply or another bind cannot change it, so both calls return a.x. The only thing that overrides a bound this is calling the bound function with new.

  15. 15.

    In strict mode (for example inside an ES module), what happens when this runs?

    mid
    const user = {
      name: 'Ada',
      greet() {
        return 'Hi ' + this.name;
      },
    };
    const greet = user.greet;
    console.log(greet());
    1. AIt logs 'Hi Ada'
    2. BIt logs 'Hi undefined'
    3. CIt throws a TypeError
    4. DIt logs 'Hi '
    Show answer

    Answer: C (It throws a TypeError)

    Assigning user.greet to a variable drops the receiver, so greet() is a plain call. In strict mode a plain call gets this === undefined, and reading undefined.name throws. Sloppy scripts would use the global object instead and log 'Hi undefined' in Node. Fix it with user.greet.bind(user) or an arrow wrapper.

  16. 16.

    What does this log?

    mid
    const parent = { greeting: 'hi' };
    const child = Object.create(parent);
    child.greeting = 'hey';
    delete child.greeting;
    console.log(child.greeting, Object.keys(child).length);
    1. Aundefined 0
    2. Bhey 1
    3. Chi 1
    4. Dhi 0
    Show answer

    Answer: D (hi 0)

    Assigning child.greeting creates an own property that shadows the inherited one; it does not change parent. delete removes only that own property, so the lookup falls through to the prototype again and finds 'hi'. Object.keys lists own enumerable keys only, so it is empty.

  17. 17.

    What does this log?

    hard
    function Animal() {}
    Animal.prototype.speak = () => 'generic';
    const a = new Animal();
    Animal.prototype = { speak: () => 'woof' };
    const b = new Animal();
    console.log(a.speak(), b.speak(), a instanceof Animal);
    1. Awoof woof true
    2. Bgeneric woof true
    3. Cgeneric woof false
    4. Dwoof woof false
    Show answer

    Answer: C (generic woof false)

    new links each instance to whatever object Animal.prototype is at that moment. a keeps its link to the old object, so it still says 'generic'. instanceof checks whether the current Animal.prototype is in a's chain, and it is not, so it returns false. Mutating the prototype would affect both; replacing it does not.

  18. 18.

    What does this log?

    hard
    class Base {
      constructor() {
        this.init();
      }
      init() {}
    }
    class Child extends Base {
      label = 'child';
      init() { console.log(this.label); }
    }
    new Child();
    1. A'child'
    2. Bundefined
    3. CIt throws a TypeError
    4. DNothing: Base's empty init runs
    Show answer

    Answer: B (undefined)

    The overridden init does run, because this is already a Child during the base constructor. But a derived class initialises its fields only after super() returns, so label has not been assigned yet and the log shows undefined. Avoid calling overridable methods from constructors.

  19. 19.

    In what order are the numbers logged?

    mid
    console.log('1');
    setTimeout(() => console.log('2'), 0);
    Promise.resolve().then(() => console.log('3'));
    queueMicrotask(() => console.log('4'));
    console.log('5');
    1. A1 2 3 4 5
    2. B1 5 2 3 4
    3. C1 5 4 3 2
    4. D1 5 3 4 2
    Show answer

    Answer: D (1 5 3 4 2)

    Synchronous code runs first (1, 5). Then the microtask queue is drained in the order tasks were queued: the promise reaction 3, then the queueMicrotask callback 4. The setTimeout callback is a macrotask, so 2 runs only after all microtasks.

  20. 20.

    What order are the letters logged in?

    mid
    async function run() {
      console.log('a');
      await null;
      console.log('b');
    }
    console.log('c');
    run();
    console.log('d');
    1. Ac a b d
    2. Bc a d b
    3. Cc d a b
    4. Da b c d
    Show answer

    Answer: B (c a d b)

    An async function runs synchronously until its first await, so a logs immediately after c. await suspends run and schedules the rest as a microtask, even for a non-promise like null. The caller continues with d, and b runs once the call stack is empty.

  21. 21.

    What order are the letters logged in?

    mid
    const p = new Promise((resolve) => {
      console.log('A');
      resolve('B');
      console.log('C');
    });
    p.then((v) => console.log(v));
    console.log('D');
    1. AA B C D
    2. BD A C B
    3. CA C D B
    4. DA C B D
    Show answer

    Answer: C (A C D B)

    The executor function runs synchronously inside new Promise, so A and C log immediately; calling resolve does not stop the executor. .then callbacks always run asynchronously as microtasks, even on an already-resolved promise, so D logs before B.

  22. 22.

    What order are the numbers logged in?

    hard
    Promise.resolve()
      .then(() => console.log(1))
      .then(() => console.log(2));
    Promise.resolve()
      .then(() => console.log(3))
      .then(() => console.log(4));
    1. A1 3 2 4
    2. B1 2 3 4
    3. C3 4 1 2
    4. D1 3 4 2
    Show answer

    Answer: A (1 3 2 4)

    Both first-level callbacks are queued as microtasks right away, so 1 and 3 run first. Each then returns a new promise, and a second-level callback is queued only when that promise resolves, which happens after 1 or 3 has run. So the chains interleave: 1 3 2 4.

  23. 23.

    What order are the labels logged in?

    hard
    setTimeout(() => {
      console.log('t1');
      Promise.resolve().then(() => console.log('p1'));
    }, 0);
    setTimeout(() => console.log('t2'), 0);
    Promise.resolve().then(() => console.log('p0'));
    1. At1 t2 p0 p1
    2. Bp0 t1 t2 p1
    3. Ct1 p1 t2 p0
    4. Dp0 t1 p1 t2
    Show answer

    Answer: D (p0 t1 p1 t2)

    After the script finishes, the pending microtask p0 runs first. Then the first timer runs and queues p1. The microtask queue is drained after every macrotask, so p1 runs before the second timer t2. Browsers and Node 11+ agree on this order.

  24. 24.

    What happens when this runs?

    mid
    async function load() {
      throw new Error('boom');
    }
    try {
      load();
      console.log('after');
    } catch (e) {
      console.log('caught');
    }
    1. AIt logs caught
    2. BIt logs after, then the rejection is reported as unhandled
    3. CIt logs after, then caught
    4. DNothing is logged, because load() throws synchronously
    Show answer

    Answer: B (It logs after, then the rejection is reported as unhandled)

    Calling an async function never throws synchronously: the throw turns into a rejected promise that the call returns. Nothing awaits it, so the try block finishes normally and the rejection goes unhandled (in Node 15+ that crashes the process). Use await load() inside an async function, or load().catch(...).

  25. 25.

    You request the same file from three mirrors and want the first successful response, ignoring failures unless every mirror fails. Which combinator fits?

    mid
    1. APromise.any
    2. BPromise.race
    3. CPromise.all
    4. DPromise.allSettled
    Show answer

    Answer: A (Promise.any)

    Promise.any fulfils with the first promise to fulfil and rejects with an AggregateError only if all of them reject. Promise.race settles with the first promise to settle, so a fast failure would win. Promise.all rejects on the first failure, and Promise.allSettled waits for every mirror.

  26. 26.

    What does this log?

    mid
    console.log(['1', '2', '3'].map(parseInt));
    1. A[1, 2, 3]
    2. B[1, NaN, NaN]
    3. C[1, NaN, 3]
    4. D[NaN, NaN, NaN]
    Show answer

    Answer: B ([1, NaN, NaN])

    map calls its callback with (value, index, array), and parseInt takes (string, radix). So the calls are parseInt('1', 0) (radix 0 means the default, giving 1), parseInt('2', 1) (radix 1 is invalid, so NaN) and parseInt('3', 2) (3 is not a binary digit, so NaN). Use .map(Number) or an arrow function.

  27. 27.

    What does this log?

    easy
    console.log([10, 1, 5, 100].sort());
    1. A[1, 5, 10, 100]
    2. B[100, 10, 5, 1]
    3. C[10, 1, 5, 100]
    4. D[1, 10, 100, 5]
    Show answer

    Answer: D ([1, 10, 100, 5])

    Without a comparator, sort converts elements to strings and compares them by UTF-16 code units, so '100' comes before '5'. Use arr.sort((a, b) => a - b) for numbers. sort also mutates the array; toSorted returns a sorted copy.

  28. 28.

    What does this log?

    mid
    const cache = {};
    const a = { id: 'a' };
    const b = { id: 'b' };
    cache[a] = 1;
    cache[b] = 2;
    console.log(cache[a]);
    1. A2
    2. B1
    3. Cundefined
    4. DIt throws a TypeError
    Show answer

    Answer: A (2)

    Plain object keys are strings or symbols. Both objects are converted with String() and become '[object Object]', so the second assignment overwrites the first and cache[a] reads 2. Use a Map when you need objects as keys.

  29. 29.

    What does this log?

    hard
    const obj = { b: 1, 2: 'two', a: 2, 1: 'one' };
    console.log(Object.keys(obj));
    1. A['b', '2', 'a', '1']
    2. B['1', '2', 'a', 'b']
    3. C['1', '2', 'b', 'a']
    4. DThe order is not guaranteed
    Show answer

    Answer: C (['1', '2', 'b', 'a'])

    Own property order is defined: integer-like keys first in ascending numeric order, then other string keys in insertion order, then symbols (which Object.keys skips). So 1 and 2 jump ahead, while b stays before a.

  30. 30.

    What does this log?

    easy
    const original = { name: 'Ada', tags: ['math'] };
    const copy = { ...original };
    copy.name = 'Grace';
    copy.tags.push('code');
    console.log(original.name, original.tags.length);
    1. AAda 2
    2. BAda 1
    3. CGrace 2
    4. DGrace 1
    Show answer

    Answer: A (Ada 2)

    Object spread makes a shallow copy. name holds a primitive, so changing copy.name does not affect the original. tags is the same array in both objects, so the push shows up in original.tags too. Use structuredClone for a deep copy.

  31. 31.

    What does this log?

    easy
    const config = Object.freeze({ theme: { dark: false } });
    config.theme.dark = true;
    console.log(config.theme.dark);
    1. Atrue
    2. Bfalse
    3. CIt throws a TypeError
    4. Dundefined
    Show answer

    Answer: A (true)

    Object.freeze is shallow. It makes config's own properties read-only, but config.theme still points to an ordinary, unfrozen object, so its dark property can change, even in strict mode. Deep freezing needs a recursive helper.

  32. 32.

    What does this log?

    mid
    const data = { a: undefined, b: () => 1, c: NaN, d: null };
    console.log(JSON.stringify(data));
    1. A{"a":null,"b":null,"c":null,"d":null}
    2. B{"c":null,"d":null}
    3. C{"c":NaN,"d":null}
    4. D{"a":undefined,"c":NaN,"d":null}
    Show answer

    Answer: B ({"c":null,"d":null})

    Object properties whose value is undefined, a function or a symbol are left out. NaN and Infinity are not valid JSON numbers, so they become null, and null stays null. Inside arrays, undefined and functions become null instead of being dropped.

  33. 33.

    Which value makes structuredClone(value) throw?

    hard
    1. AAn object with a circular reference
    2. BAn object with a method
    3. CA Date instance
    4. DAn object containing a Map and a Set
    Show answer

    Answer: B (An object with a method)

    The structured clone algorithm handles Map, Set, Date, RegExp, typed arrays and circular references, but functions cannot be cloned, so an object with a method throws a DataCloneError. It also drops prototypes, so class instances come back as plain objects.

  34. 34.

    What does this log?

    hard
    function* gen() {
      const x = yield 1;
      console.log('got', x);
      yield x * 2;
    }
    const it = gen();
    console.log(it.next(5).value);
    console.log(it.next(10).value);
    1. A1, got 5, 10
    2. B5, got 10, 20
    3. C1, got undefined, NaN
    4. D1, got 10, 20
    Show answer

    Answer: D (1, got 10, 20)

    The first next() runs the generator up to the first yield and returns 1; its argument 5 is ignored because no yield is waiting to receive it. next(10) resumes the generator, and 10 becomes the value of the paused yield 1 expression, so x is 10 and the next yield produces 20.

  35. 35.

    Both files are ES modules. What does main.js log?

    hard
    // counter.js
    export let count = 0;
    export function increment() {
      count++;
    }
    
    // main.js
    import { count, increment } from './counter.js';
    increment();
    console.log(count);
    1. A1
    2. B0
    3. CA TypeError: assignment to a constant
    4. Dundefined
    Show answer

    Answer: A (1)

    ES module imports are live, read-only bindings to the exporting module's variable, not copies, so main.js sees the updated 1. Only the exporting module can reassign it; count = 5 inside main.js would throw a TypeError. With CommonJS, destructuring the result of require() copies the value, which would stay 0.

  36. 36.

    What does this log?

    mid
    function check() {
      try {
        return 'try';
      } finally {
        return 'finally';
      }
    }
    console.log(check());
    1. A'try'
    2. BA SyntaxError
    3. C'try', then 'finally'
    4. D'finally'
    Show answer

    Answer: D ('finally')

    A finally block always runs, even after return. If it executes its own return, that completion replaces the pending one, so the function returns 'finally'. A thrown error would be silently swallowed the same way, which is why linters flag return inside finally.

  37. 37.

    In a browser, the user clicks the span. What does the listener log?

    mid
    // <ul id="list"><li><button>Save <span>icon</span></button></li></ul>
    const list = document.getElementById('list');
    list.addEventListener('click', (event) => {
      console.log(event.target.tagName, event.currentTarget.tagName);
    });
    1. AUL UL
    2. BBUTTON UL
    3. CSPAN UL
    4. DSPAN SPAN
    Show answer

    Answer: C (SPAN UL)

    event.target is the element where the event started, the innermost element clicked, here the SPAN. event.currentTarget is the element whose listener is running, the UL. That is why delegation code usually uses event.target.closest('button') to find the element it cares about.

  38. 38.

    In a modern browser, the user clicks #inner. What order are the messages logged in?

    mid
    // <div id="outer"><div id="inner">Click me</div></div>
    const outer = document.getElementById('outer');
    const inner = document.getElementById('inner');
    outer.addEventListener('click', () => console.log('outer bubble'));
    outer.addEventListener('click', () => console.log('outer capture'), true);
    inner.addEventListener('click', () => console.log('inner bubble'));
    inner.addEventListener('click', () => console.log('inner capture'), true);
    1. Ainner capture, inner bubble, outer capture, outer bubble
    2. Bouter capture, inner capture, inner bubble, outer bubble
    3. Couter bubble, outer capture, inner bubble, inner capture
    4. Douter capture, inner bubble, inner capture, outer bubble
    Show answer

    Answer: B (outer capture, inner capture, inner bubble, outer bubble)

    An event travels down from the window in the capture phase, reaches the target, then bubbles back up. Capture listeners on ancestors fire first, then the listeners on the target, then bubble listeners on ancestors. On the target itself, current browsers also run capture listeners before bubble ones (older browsers used registration order).

  39. 40.

    In a browser, which storage is scoped to a single tab and cleared when that tab is closed?

    easy
    1. AlocalStorage
    2. BsessionStorage
    3. CA cookie without Expires or Max-Age
    4. DIndexedDB
    Show answer

    Answer: B (sessionStorage)

    sessionStorage is per origin and per tab: another tab on the same site gets its own copy, and the data survives reloads but not closing the tab. localStorage and IndexedDB persist and are shared across tabs. A session cookie lives until the browser session ends and is shared by every tab.

  40. 41.

    In a browser, what does this log?

    easy
    localStorage.setItem('user', { name: 'Ada' });
    console.log(localStorage.getItem('user'));
    1. A{ name: 'Ada' }
    2. BThe string [object Object]
    3. Cnull
    4. DIt throws a TypeError
    Show answer

    Answer: B (The string [object Object])

    Web Storage only stores strings, and setItem converts its value with String(), so the object is saved as '[object Object]'. Store objects with JSON.stringify and read them back with JSON.parse.

  41. 42.

    Why is a WeakMap a good fit for storing metadata about DOM nodes?

    mid
    1. AIt can be iterated to list every node that has metadata
    2. BIts lookups are much faster than those of a Map
    3. CIts keys are held weakly, so an entry can be garbage-collected once its node is otherwise unreachable
    4. DIt accepts strings, so you can key it by element id
    Show answer

    Answer: C (Its keys are held weakly, so an entry can be garbage-collected once its node is otherwise unreachable)

    A Map holds strong references to its keys, so DOM nodes used as keys can never be collected after removal: a leak. A WeakMap does not keep its keys alive, and when a key is collected its value goes with it. The price is that keys must be objects (or non-registered symbols), and there is no iteration or size.

esc