Top 20 JavaScript interview questions with answers: core JS, Node.js and React
20 JavaScript interview questions with answers: 10 on core JavaScript, 5 on Node.js and 5 on React, covering closures, hoisting, the event loop, promises, Express errors and React state.
CoversArraysAsync codeClosuresCore JavaScriptEqualityError handlingEvent loopExpressFunctionsHoistingHooksImmutabilityListsNode.jsPerformancePromisesReactRenderingScopeStatethisTypesWorker threads
By Deepak KumarPublished Answers checked by Deepak
Take it as a timed test
- 20 questions
- 20 min
- Pass mark 60%
- Answers shown at the end
No sign-up. Your answers and scores stay in this browser.
This quiz follows the shape of a typical JavaScript developer interview: 10 questions on the core language, then 5 on Node.js and 5 on React. It mixes single-answer, select-all, true-or-false, matching and predict-the-output questions, at fresher and mid level.
What does it cover?
- Core JavaScript (10):
typeof, equality and falsy values, hoisting, closures,thisin arrow functions, the event loop,Promise.alland array methods. - Node.js (5): the single thread and libuv,
nextTickversus promises versussetImmediate, blocking calls, async errors in Express and CPU-heavy work. - React (5): keys, batched state updates, hooks, stale closures in effects and immutable state updates.
How should you use it?
Go through the practice questions first and commit to an answer before you open each explanation. Then take the timed test: 20 questions in 20 minutes, with a pass mark of 60%. Your report shows which area you lost marks in, so you know whether to revise the core language, Node.js or React first, and you can retry only the questions you missed.
Practice questions with answers
Try each one before opening the answer. The explanation covers why the other options are wrong.
What does typeof null return?
console.log(typeof null);Show answer and explanation
Answer: A. "object"
typeof null returns "object". This is a bug from the first version of JavaScript that was never fixed, because changing it would break existing websites. null is still a primitive value, not an object. To check for it, compare directly with value === null. "undefined" is what typeof returns for an undeclared or unassigned variable, and typeof never throws, even for names that were never declared.
What does a var loop with setTimeout log?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}Show answer and explanation
Answer: A. 3 3 3
var is function-scoped, so all three callbacks close over the same i. The timers run only after the loop has finished, when i is already 3, so it logs 3 3 3. Changing var to let prints 0 1 2, because let creates a new binding for each loop iteration. Interviewers use this to test both closures and how the event loop delays timer callbacks.
What do 0 == '' and 0 === '' return?
console.log(0 == '', 0 === '');Show answer and explanation
Answer: A. true false
== converts both sides to a common type before comparing: the empty string becomes the number 0, so 0 == '' is true. === compares without any conversion, and a number is never strictly equal to a string, so it is false. This hidden conversion is why most style guides ask for === everywhere. The common exception is value == null, which matches both null and undefined.
Variables declared with let are not hoisted at all.
TrueFalse
Show answer and explanation
Answer: False
False. let and const declarations are hoisted to the top of their block, but they are not initialised. From the start of the block until the declaration line runs, the variable is in the temporal dead zone, and reading it throws a ReferenceError. var is also hoisted, but it is initialised to undefined, which is why reading a var early gives undefined instead of an error.
What is a closure in JavaScript?
Show answer and explanation
Answer: A. A function together with the variables from the scope it was created in, which it can still use after that scope has finished
Every function keeps a reference to the variables of the scope where it was defined. When an outer function returns an inner function, the inner one can still read and update those variables. That combination is a closure. Closures power private state (a counter only its own functions can change), event handlers, memoisation and many module patterns. Freezing, curly-brace blocks and run-once functions are unrelated ideas that only sound similar.
In what order do a timer, a promise and two logs run?
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Show answer and explanation
Answer: A. A D C B
Synchronous code runs first, so A and D are logged straight away. Promise callbacks go into the microtask queue, and timer callbacks go into the task (macrotask) queue. After the current script finishes, the engine empties the whole microtask queue before it takes the next task, so C comes before B, even though the timer was set to 0 ms. A delay of 0 means "as soon as possible after this", not "immediately".
Which of these values are falsy? Select all that apply.
Show answer and explanation
Answer: A. 0 + D. null + E. NaN + F. "" (an empty string)
JavaScript has a short list of falsy values: false, 0, -0, 0n, "", null, undefined and NaN. Everything else is truthy. That includes the string "0", because it is a non-empty string, and an empty array [], because every object is truthy. This is why if (list) does not detect an empty array. Check list.length instead.
What value does this have inside an arrow function?
Show answer and explanation
Answer: A. The this of the scope where the arrow function was defined
Arrow functions do not have their own this. They take it from the surrounding scope at the point where they are defined, and call, apply or bind cannot change it. That makes them a good fit for callbacks inside methods. It also makes them a poor choice for object methods that need the object as this, and they cannot be used as constructors. A regular function, by contrast, gets this from how it is called.
How does Promise.all behave if one of the promises rejects?
Show answer and explanation
Answer: A. It rejects straight away with that error, without waiting for the others
Promise.all fails fast: as soon as any promise rejects, the combined promise rejects with that error. The other operations keep running, but their results are discarded. When you need every outcome, use Promise.allSettled, which waits for all of them and returns { status, value } or { status, reason } for each. Promise.any resolves with the first success, and Promise.race settles with whichever promise settles first.
Match each array method to what it returns.
- map
- filter
- reduce
- forEach
- A new array with only the items that pass a test
- A new array with every item transformed
- undefined: it only runs a function for each item
- A single value built up from all the items
Show answer and explanation
Answer: map → A new array with every item transformedfilter → A new array with only the items that pass a testreduce → A single value built up from all the itemsforEach → undefined: it only runs a function for each item
map returns a new array of the same length with each item transformed. filter returns a new array containing only the items whose callback returned a truthy value. reduce folds the array into one value, such as a sum or an object built from the items. forEach returns undefined, so it is only for side effects. A common mistake is chaining .forEach().map() and expecting an array back.
Node.js cannot do any work in parallel because JavaScript runs on a single thread.
TrueFalse
Show answer and explanation
Answer: False
False. Your JavaScript runs on one main thread, but Node.js hands slow I/O to the operating system and to libuv's thread pool, which handles file system work, DNS lookups, compression and some crypto. The event loop picks up the results when they are ready. That is how one Node.js process serves thousands of connections at once. For CPU-heavy JavaScript you can add real parallelism with worker_threads or by running several processes.
Run as a CommonJS script (node app.js), in what order does this log sync, nextTick, promise and immediate?
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');Show answer and explanation
Answer: A. sync, nextTick, promise, immediate
Synchronous code always runs first, so sync comes first. When it finishes, Node.js empties the process.nextTick queue before the promise microtask queue, so nextTick comes before promise. setImmediate callbacks run in the check phase of the next turn of the event loop, after all microtasks, so immediate is last. Overusing nextTick can starve I/O, which is why queueMicrotask is usually the better choice. Watch out: in an ES module (.mjs or "type": "module"), top-level code already runs as a microtask, so there promise logs before nextTick.
Why should you avoid fs.readFileSync inside an HTTP request handler?
Show answer and explanation
Answer: A. It blocks the event loop, so every other request waits until the file has been read
A synchronous call stops the single JavaScript thread until the disk read finishes. In a server, that means every other user's request waits too, and under load latency climbs for everyone. Use fs.promises.readFile with await, or a stream for large files. Sync APIs are not deprecated, have no 1 MB limit and read any file type: they are fine in startup code and CLI scripts, where nothing else is waiting.
In Express 4, which of these correctly send an error from an async route handler to the error-handling middleware? Select all that apply.
Show answer and explanation
Answer: A. Wrap the handler body in try/catch and call next(err) in the catch + B. Attach .catch(next) to the promise the handler returns
Express 4 does not look at the promise an async handler returns, so a rejection never reaches your error middleware: the request hangs, and Node.js logs an unhandled rejection. Catch it yourself and call next(err), or use a small wrapper that adds .catch(next). Express 5 does forward rejected promises automatically, which is a common follow-up question. Exiting the process takes the whole server down for one bad request.
A Node.js API resizes images in JavaScript and every other request slows down while it runs. What is the best fix?
Show answer and explanation
Answer: A. Move the resizing to worker_threads (or a job queue) so it runs off the main thread
Resizing is CPU work, so it keeps the main thread busy and blocks the event loop. Marking a function async does not move CPU work to another thread: it still runs on the main thread. A longer timeout only hides the problem, and nextTick just reorders when the work starts. A worker thread, or a separate worker process fed by a queue, runs the work in parallel and keeps the API responsive.
What does the key prop help React do when rendering a list?
Show answer and explanation
Answer: A. Tell which items changed, were added or were removed between renders
Keys give each element a stable identity between renders, so React can match old and new children and then move, insert or remove only the DOM nodes that changed. Without stable keys, React may reuse the wrong component and its state, such as text typed into an input. Use an ID from your data. The array index is only safe when the list never reorders. Keys have nothing to do with styling, focus or sorting.
If count is 0 and this click handler runs once, what is count after the next render?
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}Show answer and explanation
Answer: A. 1
All three calls read the same count from this render, which is 0, so each one asks React to set the value to 1. React batches the updates and renders once, with count equal to 1. To add three, use the updater form setCount(c => c + 1). React passes each updater the latest queued value. This question checks whether you know that state is a snapshot for each render.
Match each React hook to what it is for.
- useRef
- useMemo
- useCallback
- useContext
- Cache a function between renders
- Keep a mutable value that doesn't cause a re-render
- Read a value from the nearest provider
- Cache a computed value between renders
Show answer and explanation
Answer: useRef → Keep a mutable value that doesn't cause a re-renderuseMemo → Cache a computed value between rendersuseCallback → Cache a function between rendersuseContext → Read a value from the nearest provider
useRef holds a mutable .current that survives renders without causing one, which makes it useful for DOM nodes and timer IDs. useMemo caches the result of an expensive calculation, and useCallback caches the function itself, so a child wrapped in React.memo does not re-render because of a new function prop. useContext reads the value from the closest matching provider above the component.
This counter shows 1 and then stops increasing. Why?
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(id);
}, []);Show answer and explanation
Answer: A. The interval callback captured count from the first render, so it keeps setting 0 + 1
With an empty dependency array, the effect runs once, and its callback closes over count from the first render, which is 0. Every tick calls setCount(0 + 1), so the value stays at 1. This is called a stale closure. Fix it with the updater form, setCount(c => c + 1), which needs no dependency. The cleanup only runs on unmount, and React handles updates from timers normally.
Calling items.push(newItem) and then setItems(items) re-renders the component with the new item.
TrueFalse
Show answer and explanation
Answer: False
False. push changes the existing array, so setItems(items) passes React the same reference it already has. React compares with Object.is, sees no change and skips the re-render, which leaves the UI out of date. Always create a new array: setItems([...items, newItem]), or setItems(prev => [...prev, newItem]). The same rule applies to objects: copy, then change the copy.
Answered them all? Take the same questions against the clock and get a topic-by-topic report.
Start the timed test