InterviewGrind Get notified

InterviewGrind › JavaScript

JavaScript Interview Questions and Answers

Closures, async/await, the event loop, coercion, React pitfalls and TypeScript: JavaScript interview questions built around real bugs, with every answer explained.

15 free questions from the app. Try each one before you open the answer.

1. VariablesEasy

What does const actually guarantee?

  1. The value can never change, not even the contents of an object it holds
  2. The variable can't be reassigned, but an object it holds can still change
  3. The value is computed once at compile time and inlined
  4. The variable is global and shared across all modules
Show answer

Answer: B. The variable can't be reassigned, but an object it holds can still change

const freezes the binding, not the value. Default to const, use let when you need to reassign, and avoid var.

2. Array sortEasy

For scores [9, 10, 100, 25], the leaderboard shows 9, 25 and 100 as the top three.

  1. function topScores(scores) {
  2. return scores.sort().reverse().slice(0, 3);
  3. }

Which line has the bug?

Show answer

Answer: Line 2: return scores.sort().reverse().slice(0, 3);

Without a comparator, sort compares elements as strings, so "10" < "100" < "25" < "9". Use scores.sort((a, b) => b - a). Note that sort also mutates the caller's array; toSorted doesn't.

3. Event loopEasy

Myth or fact: async functions run on a separate thread.

  1. Myth
  2. Fact
Show answer

Answer: A. Myth

Myth. An async function runs on the same single thread; await just pauses it and lets the event loop run other work until the promise settles. CPU-heavy code still blocks; move it to a Worker.

4. Environment variablesEasy

The new checkout is on in production even though the deploy sets NEW_CHECKOUT=false.

  1. function checkoutVersion() {
  2. if (process.env.NEW_CHECKOUT) {
  3. return 'v2';
  4. }
  5. return 'v1';
  6. }

Which line has the bug?

Show answer

Answer: Line 2: if (process.env.NEW_CHECKOUT) {

Environment variables are always strings, and "false" is a non-empty string, so it’s truthy. Compare explicitly: process.env.NEW_CHECKOUT === "true", ideally in one config module that parses every setting.

5. CoercionEasy

The updater says version 1.9.2 is newer than 1.10.0.

  1. function isNewer(a, b) {
  2. return a.version > b.version;
  3. }

Which line has the bug?

Show answer

Answer: Line 2: return a.version > b.version;

Version strings compare character by character, and "9" > "1". Converting to a number doesn't help with three parts. Split on ".", compare each part as a number, and stop at the first difference.

6. TypesEasy

The config validator accepts null as a settings object, and the app crashes reading a property from it.

  1. function isObject(value) {
  2. return typeof value === 'object';
  3. }

Which line has the bug?

Show answer

Answer: Line 2: return typeof value === 'object';

typeof null is "object", a quirk kept since the first JavaScript. Check for null too: value !== null && typeof value === 'object' (and add !Array.isArray(value) if arrays shouldn't count).

7. Closures and scopeEasy

This logs 3, 3, 3. Change one word so it logs 0, 1, 2.

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i));
}
  1. var → let
  2. setTimeout → setInterval
  3. i++ → ++i
  4. console.log → console.info
Show answer

Answer: A. var → let

let creates a fresh i for each loop iteration, so each callback closes over its own value. var shares one variable that's 3 by the time the callbacks run.

8. Parsing numbersEasy

An import reads amounts like "1,234.50". What do parseFloat and Number make of it?

console.log(parseFloat('1,234.50'), Number('1,234.50'));

What does it output?

  1. 1 NaN
  2. 1234.5 1234.5
  3. NaN NaN
  4. 1234.5 NaN
Show answer

Answer: A. 1 NaN

parseFloat reads as far as it can and stops at the comma, giving 1, a silent and wrong answer. Number rejects the whole string with NaN, which at least fails loudly. Strip the grouping separators for the source locale before parsing.

9. Currency formattingEasy

Prices should show as "$1,234.50" for US users and "1.234,50 €" for German users. What’s the right tool?

  1. Intl.NumberFormat with the locale and style: "currency"
  2. toFixed(2), then replace the dots with commas for Germany
  3. A regex that inserts a thousands separator every three digits
  4. toLocaleString() with no arguments, which picks the right currency for you
Show answer

Answer: A. Intl.NumberFormat with the locale and style: "currency"

new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(1234.5) gives "1.234,50 €". Create the formatter once and reuse it; the currency must come from your data, not the locale.

10. NumbersEasy

Why is 0.1 + 0.2 === 0.3 false in JavaScript?

  1. JavaScript has a rounding bug in its addition operator for decimals
  2. 0.1 and 0.2 can't be represented exactly in binary floating point
  3. === compares object references, not values
  4. Numbers are stored as strings
Show answer

Answer: B. 0.1 and 0.2 can't be represented exactly in binary floating point

All numbers are IEEE 754 doubles. 0.1 + 0.2 is 0.30000000000000004. Compare with a tolerance: Math.abs(a - b) < Number.EPSILON.

11. ClosuresEasy

Each widget should count its own clicks, but clicking one widget bumps the number on all of them.

  1. let clicks = 0;
  2. function makeWidget(el) {
  3. el.addEventListener('click', () => {
  4. clicks++;
  5. el.textContent = clicks;
  6. });
  7. }

Which line has the bug?

Show answer

Answer: Line 1: let clicks = 0;

Every handler closes over the one clicks declared outside the factory. Declare let clicks = 0; inside makeWidget so each widget gets its own counter.

12. HoistingEasy

The startup log says "connecting to undefined", yet the connection works.

  1. function connect() {
  2. console.log(`connecting to ${host}`);
  3. var host = process.env.DB_HOST;
  4. return open(host);
  5. }

Which line has the bug?

Show answer

Answer: Line 2: console.log(`connecting to ${host}`);

var host is hoisted to the top of the function as undefined, and the assignment happens a line later. Log after assigning. With const, reading it early throws a ReferenceError that points straight at the mistake.

13. TypesEasy

The validator rejects every list of tags. What fixes it?

if (typeof tags !== 'array') throw new Error('tags must be a list');
  1. typeof tags !== 'array' → !Array.isArray(tags)
  2. !== → !=
  3. 'array' → 'Array'
  4. typeof tags → tags.constructor
Show answer

Answer: A. typeof tags !== 'array' → !Array.isArray(tags)

Arrays are objects, so typeof says 'object'. Array.isArray is the reliable check, even across iframes.

14. Removing array itemsEasy

Removing an item leaves an "empty" gap, and the list still reports the same length. What fixes it?

delete items[i];
  1. delete items[i] → items.splice(i, 1)
  2. delete items[i] → items[i] = null
  3. delete items[i] → items.slice(i, 1)
  4. delete items[i] → delete items[i - 1]
Show answer

Answer: A. delete items[i] → items.splice(i, 1)

delete removes a property, leaving a hole in the array. splice(i, 1) removes the element and shifts the rest; items.filter((_, j) => j !== i) does it without mutating.

15. Number formattingEasy

withTax(10) returns "101.30" instead of 11.3. What fixes it?

function withTax(price) {
  const tax = (price * 0.13).toFixed(2);
  return price + tax;
}
  1. (price * 0.13).toFixed(2) → Math.round(price * 13) / 100
  2. (price * 0.13).toFixed(2) → (price * 0.13).toFixed(0)
  3. return price + tax → return ${price + tax}``
  4. return price + tax → return price.concat(tax)
Show answer

Answer: A. (price * 0.13).toFixed(2) → Math.round(price * 13) / 100

toFixed returns a *string*, and 10 + "1.30" concatenates. Keep money as numbers (ideally integer cents) and format with toFixed or Intl.NumberFormat only when displaying.

Practise like this every day

InterviewGrind gives you ten questions like these each day on your iPhone, with Pro tracks for company-style loops.

Get the launch email