JavaScript Interview Questions: Core Concepts & Practice Quiz

Reviewed by Mark Dickie · Last updated

JavaScript is a single-threaded, interpreted scripting language that runs in the browser and on the server via Node.js, built around an event-driven, non-blocking I/O model. For interviews at any level, you should be confident on the core type system and coercion rules, how closures and scope chains work, the event loop and the call stack, and the async primitives — callbacks, Promises, and async/await. Prototype-based inheritance trips up a lot of candidates too, so if you can explain [[Prototype]] without reaching for a metaphor, you are already ahead of most people in the room.

What interviewers actually test

The questions tend to cluster around a handful of topics regardless of the company. Here is a map of those areas and the underlying concept each one is really probing:

| Topic area | Core concept being probed | |---|---| | var / let / const | Hoisting, temporal dead zone, block vs. function scope | | Closures | Lexical environment, memory, factory functions | | this keyword | Binding rules: default, implicit, explicit (call/apply/bind), new | | Prototypes & classes | [[Prototype]] chain, Object.create, ES2015 class as syntax sugar | | Event loop | Call stack, task queue, microtask queue, Promise resolution order | | Async patterns | Callbacks → Promises → async/await, error handling with try/catch | | Type coercion | Loose equality (==), typeof, Number() vs + vs parseInt | | Array/object methods | map, reduce, filter, flat, Object.keys, spread vs. rest |

How to pace your preparation

The difficulty range here runs from beginner syntax recall up to problems that require you to reason about engine internals. A good order of study:

  1. Types and coercion first. Coercion catches people off guard more than any other topic. Nail == vs ===, falsy values, and typeof null before anything else.
  2. Scope and closures. Read the spec definition of a lexical environment, then write three closure-based examples from scratch — a counter, a memoizer, and a partial application function.
  3. this binding. Work through each of the four binding rules in order: default, implicit, explicit, new. Arrow functions get a separate rule: they inherit this from their enclosing lexical scope.
  4. The event loop. Draw the call stack and both queues on paper. Then predict the console output of a short snippet mixing setTimeout(fn, 0), a resolved Promise, and a synchronous console.log. Check your answer before running it.
  5. Prototype chain and class. Write an inheritance example with plain Object.create, then rewrite it with class/extends. Confirming they produce the same prototype chain is the whole point.

ES2023 (the current major release as of this writing) added Array.prototype.toSorted, toReversed, toSpliced, and with — non-mutating counterparts to the old mutating methods. Those are fresh enough that an interviewer asking about immutable array operations might expect you to know them.

At a glance

Questions15
Difficulty2–5 of 5
FormatsMultiple choice, Multiple answer, True / false, Code output, Find the bug, Short answer, Flashcard

What you'll review

  1. primitive types
  2. equality operators
  3. type coercion
  4. array methods
  5. var let const
  6. nan handling
  7. promise ordering
  8. higher order functions
  9. closures
  10. this rules
  11. call apply bind
  12. hoisting
  13. tdz
  14. new operator
  15. optional chaining

Practice questions

JavaScript/types-coercion/primitive-types

What does typeof null evaluate to?

Options

  • "object"
  • "null"
  • "undefined"
  • "number"
Show answer

It evaluates to the string object. This is a long-standing bug from the very first JavaScript implementation that has been kept for backward compatibility, so null reports as type object despite being a primitive. To reliably test for null, compare directly with value === null.

Why:

typeof null returns "object" — a long-standing bug from the first JS implementation that is kept for backward compatibility. Use value === null to test for null.

JavaScript/types-coercion/equality-operators

Which of these comparisons evaluates to false?

Options

  • 0 == '0'
  • 0 == ''
  • '' == '0'
  • 0 == false
Show answer

'' == '0' is the one that is false. When both operands are strings, loose equality does no coercion — it just compares the two strings, and an empty string differs from '0'. The other comparisons each coerce their operands to the number 0, which compares equal.

Why:

== between two strings does no coercion: '' and '0' are simply different strings, so it is false. The other three coerce their operands to the number 0, which compares equal.

JavaScript/types-coercion/type-coercion

Which of these values are falsy in JavaScript?

Options

  • 0
  • '0'
  • []
  • NaN
Show answer

The falsy values here are 0 and NaN. JavaScript's complete falsy set is false, 0, -0, 0n, the empty string, null, undefined, and NaN. The string '0' is a non-empty string and an empty array [] is an object, so both of those are truthy.

Why:

The falsy values are false, 0, -0, 0n, '', null, undefined, and NaN. The string '0' and an empty array [] are both objects/non-empty strings and are truthy.

JavaScript/arrays/array-methods

Which array methods mutate the array they are called on?

Options

  • map
  • splice
  • sort
  • filter
Show answer

splice and sort are the mutating methods — they change the array in place. map and filter are non-mutating: each returns a brand-new array and leaves the original untouched. This distinction matters when you rely on the source array staying intact after the call.

Why:

splice and sort mutate the array in place. map and filter are non-mutating — they return a new array and leave the original untouched.

JavaScript/scope-closures/var-let-const

Declaring an object with const makes the object immutable.

Show answer

False. const only prevents reassigning the binding itself — the object it points to can still have its properties added, changed, or deleted. If you need the contents to be immutable, use Object.freeze for a shallow freeze of the object's own properties.

Why:

const only prevents reassigning the binding. The object it points to can still have its properties changed. Use Object.freeze for shallow immutability.

JavaScript/types-coercion/nan-handling

NaN === NaN evaluates to false.

Show answer

True. NaN is the only JavaScript value that is not equal to itself, so NaN === NaN is false. This is why you cannot test for it with x === NaN; use Number.isNaN(x) instead, which correctly reports whether a value is the special Not-a-Number value.

Why:

NaN is the only JavaScript value that is not equal to itself. Use Number.isNaN(x) (not x === NaN) to test for it.

JavaScript/event-loop/promise-ordering

In what order are the four values logged?

console.log('a');
setTimeout(() => console.log('b'), 0);
Promise.resolve().then(() => console.log('c'));
console.log('d');
Show answer
a
d
c
b
Why:

Synchronous code runs first (a, d). The microtask queue (the resolved Promise's .then) drains before the next macrotask, so c logs before the setTimeout callback b.

JavaScript/functions/higher-order-functions

doubled logs [undefined, undefined, undefined]. What is wrong?

const nums = [1, 2, 3];
const doubled = nums.map(n => { n * 2 });
console.log(doubled);

Options

  • The arrow uses a block body { ... } with no return, so every element is undefined
  • map mutates nums instead of returning a new array
  • map cannot be called on an array literal
  • console.log needs JSON.stringify to print an array
Show answer

The arrow uses a block body { ... } with no return, so every element is undefined

Why:

A block-bodied arrow n => { n * 2 } has a statement body and no return, so it returns undefined for each element. Use the concise body n => n * 2 (or add return n * 2).

JavaScript/scope-closures/closures

What is a closure, and give one practical use for it.

Show answer

A closure is a function bundled together with references to its surrounding lexical scope, so it can keep accessing those variables even after the outer function has returned. A common use is data privacy — e.g. a counter factory that keeps count private and exposes only an increment function.

Why:

Closures capture variables by reference from the scope in which a function was defined. They power module patterns, data privacy, partial application, and stable callbacks.

JavaScript/this-binding/this-rules

In strict mode, what is this inside a regular function called as a standalone function (e.g. fn())?

Show answer

undefined. In strict mode this is not coerced to the global object, so a plain function call leaves it undefined.

Why:

Outside strict mode the same call would set this to the global object (window/globalThis). Arrow functions ignore call-site this entirely and use the enclosing scope's.

JavaScript/this-binding/call-apply-bind

Name the three function methods that explicitly set this, and how they differ.

Show answer

call(thisArg, ...args) and apply(thisArg, argsArray) invoke immediately (args spread vs. array); bind(thisArg, ...args) returns a new function with this permanently fixed.

Why:

call and apply differ only in how arguments are passed. bind does not invoke — it produces a bound function you call later.

JavaScript/scope-closures/hoisting

Given console.log(typeof a, typeof b); var a = 1; function b() {} at the top level, what is logged?

Options

  • undefined function
  • function function
  • undefined undefined
  • A ReferenceError is thrown
Show answer

It logs undefined function. Hoisting raises declarations to the top of the scope, but a function declaration is hoisted together with its body, so b is already callable. A var is hoisted too, yet only initialized to undefined until its assignment runs — so typeof a reports undefined.

Why:

Hoisting raises declarations to the top of the scope. A function declaration is hoisted with its body, so b is already a function. A var is hoisted but initialized to undefined until its assignment runs, so typeof a is "undefined".

JavaScript/scope-closures/tdz

What happens when this runs? { console.log(x); let x = 5; }

Options

  • It throws a ReferenceError because x is in the temporal dead zone
  • It logs undefined
  • It logs 5
  • It throws a SyntaxError
Show answer

It throws a ReferenceError. A let binding is hoisted to the top of its block but stays uninitialized in the temporal dead zone until its declaration is evaluated. Reading x before that declaration hits the dead zone and throws — unlike var, which would simply log undefined.

Why:

let and const bindings are hoisted to the top of their block but stay uninitialized — the temporal dead zone — until the declaration is evaluated. Reading x before let x = 5 throws a ReferenceError, unlike var which would log undefined.

JavaScript/prototypes/new-operator

What does new Make() produce? function Make() { this.a = 1; return { a: 2 }; }

Options

  • { a: 2 }
  • { a: 1 }
  • undefined
  • A new empty object {}
Show answer

It produces { a: 2 }. new creates a fresh object bound to this, but when a constructor explicitly returns an object, that returned object replaces the freshly-created this. Here Make returns { a: 2 }, so that wins. A returned primitive would instead be ignored and this kept.

Why:

new creates a fresh object bound to this, but if the constructor explicitly returns an object, that object replaces the newly-created this. Since Make returns { a: 2 }, that is the result. (A returned primitive would be ignored and this kept.)

JavaScript/objects/optional-chaining

Given const obj = {};, what does obj.foo?.() evaluate to?

Options

  • undefined
  • It throws TypeError: obj.foo is not a function
  • null
  • An empty object {}
Show answer

It evaluates to undefined. The optional call ?.() short-circuits when the thing being invoked is null or undefined: rather than throwing a TypeError, the whole expression simply yields undefined. Because obj.foo is undefined here, no call is attempted at all.

Why:

The optional call ?.() short-circuits when the thing being called is null or undefined: instead of throwing, the whole expression evaluates to undefined. Since obj.foo is undefined, no call is attempted.

Sources

The official documentation these questions are checked against:

Related interview questions

Job market

See javascript salaries and hiring demand from live job postings.

Practice this for real

CodePrep turns your target job description into an adaptive quiz from a bank of tagged questions, scores your answers, and resurfaces the topics you miss.

New topics and job-market signal, in your inbox

Occasional updates — new question topics, launch news, and what the developer job market is hiring for. Confirm your email to join, and unsubscribe anytime.