JavaScript 與 React 的 4 道輸出預測題檢驗理解而非記憶
Predict the Output: 4 JavaScript and React Quiz Questions That Test Understanding, Not Memory
文章提供 4 道 JavaScript 與 React 輸出預測題,分別測試事件循環、var 閉包、React 狀態更新及 effect 中的過期閉包,並附答案與解釋。
You can write JavaScript every day and still be surprised by the order a few lines log in. Syntax is easy to remember, but behaviour is harder: when a callback runs, which variable a closure captures, and what React does with three setState calls in a row.
The quickest way to find out which of those you really understand is to predict the output before running the code. If your prediction and your reasoning are both right, you understand the concept. If only the prediction is right, you have a gap that hasn't caused a bug yet.
This post has four questions of that kind, two in JavaScript and two in React. Each comes with the answer, a short explanation and the concept it tests. A companion repository has twelve more, with links to the official documentation.
Summary table
| # | Concept | What the question tests | Practical relevance |
|---|---|---|---|
| 1 | Event loop | Ordering of sync code, microtasks and timers | Explains why a .then() runs before a setTimeout(…, 0)
|
| 2 | Closures and var
|
Which binding a callback captures | A classic source of "every handler sees the last value" bugs |
| 3 | React state updates | State as a snapshot during one render | Why repeated setCount(count + 1) calls don't add up |
| 4 | Stale closures in effects | What an effect with [] dependencies can see |
Timers and subscriptions that "freeze" at an old value |
Why quiz yourself at all?
A quiz question can't tell you how good a developer you are. What it can do well is narrower and more useful:
- Find gaps cheaply. A wrong prediction in a five-line snippet costs nothing. The same misunderstanding in production code costs a debugging session.
- Separate recall from reasoning. Writing down why you picked an answer shows you whether you knew it or recognised it.
- Rehearse explaining. Technical interviews often ask you to talk through what code does. Explaining a short snippet out loud is good practice for that.
Treat each question below as a test of one concept. Write your answer down before you open the explanation.
Question 1: Sync, microtasks and timers
Concept tested: the event loop
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
queueMicrotask(() => console.log('D'));
console.log('E');
What gets logged, in order?
- A.
A B C D E - B.
A E B C D - C.
A E C D B - D.
A C D E B
Answer
C. A E C D B
The synchronous code runs first, logging A and then E. While it runs, three callbacks are queued. The setTimeout callback goes into the task (macrotask) queue. The .then() callback and the queueMicrotask callback go into the microtask queue, in that order.
When the script finishes, the runtime drains the entire microtask queue before picking up the next task. That logs C, then D. Only then does the timer callback run and log B. A delay of 0 means "as soon as a task can run", not "immediately".
Why it matters: if you've ever seen state read "too early" in a setTimeout or "too late" after an await, this ordering is usually why. MDN's microtask guide covers it in depth.
Question 2: Closures in a var loop
Concept tested: closures and function-scoped variables
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
- A.
0 1 2 - B.
3 3 3 - C.
2 2 2 - D.
undefined undefined undefined
Answer
B. 3 3 3
var is function-scoped, so there is a single i shared across all three iterations. Each arrow function closes over that same variable, not over its value at the time. By the time the timers fire, the loop has finished and i is 3.
Change var to let and the output becomes 0 1 2. A let declaration in a for loop header creates a new binding for each iteration, so each callback captures its own i.
Why it matters: the same mistake shows up in event handlers attached in loops and in async callbacks inside for loops. See MDN on closures.
Question 3: Three updates in one click
Concept tested: React state as a snapshot
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return <button onClick={handleClick}>{count}</button>;
}
After one click, what does the button show?
- A.
3 - B.
1 - C.
0 - D. React warns about too many updates and shows
0
Answer
B. 1
During a render, count is a constant: it's 0 for the entire handleClick call. Each line therefore asks React to set the state to 0 + 1. React batches the three requests and re-renders once with 1.
To build on the previous value, pass an updater function:
setCount((c) => c + 1);
setCount((c) => c + 1);
setCount((c) => c + 1); // now shows 3 after one click
React queues updater functions and applies each one to the result of the previous one.
Why it matters: this explains a whole class of "my counter or cart total is off by some amount" bugs. The React docs cover it in Queueing a series of state updates.
Question 4: An interval that stops counting
Concept tested: stale closures in effects
function Ticker() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <span>{count}</span>;
}
What does the component show after five seconds?
- A.
5 - B.
1 - C.
0 - D. It re-renders in an infinite loop
Answer
B. 1
With an empty dependency array, the effect runs once after mount, and its interval callback closes over the count from that first render, which is 0. Every second it calls setCount(0 + 1). The first call changes the state to 1. After that, each call sets the same value, so the display stays at 1.
There are two common fixes. You can use the updater form, setCount((c) => c + 1), which doesn't need count at all. Or you can list count as a dependency, which restarts the interval on every change. For a timer, the updater form is simpler.
(In development with <StrictMode>, React runs the effect, cleans it up and runs it again. The cleanup clears the first interval, so the result is the same.)
Why it matters: the same pattern affects WebSocket handlers, event listeners and polling loops set up inside effects. The React docs explain it under Removing effect dependencies.
Key takeaways for developers
-
Explain, don't just answer. If you can't say why
Clogs beforeB, a right answer is a guess. Mark it and come back to it. -
Microtasks run before the next task. Promise callbacks,
queueMicrotaskandawaitcontinuations all run before anysetTimeoutcallback, however short the delay. -
Closures capture variables, not values. Whether that variable is shared (
var) or new for each iteration (let) changes the output. -
In React,
stateis fixed for the duration of a render. When the next value depends on the previous one, use an updater function, especially inside timers and effects. - Sort your misses by concept. Two wrong answers about the event loop say more than one wrong answer each on unrelated topics. Study the concept, then try fresh questions on it.
Twelve more questions on GitHub
The companion repository of JavaScript and React quiz questions has twelve more predict-the-output questions, all different from the ones above. They cover typeof, the temporal dead zone, where async functions pause, map(parseInt), reduce on empty arrays, coercion, shallow copies, this in callbacks, Promise.allSettled, and three React behaviours. Each answer links to MDN or react.dev, and the README ends with a guide to grouping your misses by concept.
Want a bigger question bank?
If you'd like to keep going after these sixteen questions, JourneyUncommon is an interview-practice site with a multiple-choice question bank organised by language and topic. It covers JavaScript, TypeScript, React, Python, SQL, Go and more. You can browse the JavaScript questions or the React hooks questions without signing in, and the code is shown on the page.
A free account lets you answer questions and see your score. It also tracks your accuracy by topic and brings questions you got wrong back for review later. There's also a daily question and a bug arena, where you diagnose a program that gives the wrong output, with up to three hints. A paid Pro plan raises the daily limits on the AI features, but you don't need it to practise the multiple-choice questions.
Wrapping up
How many did you get right with the right reasoning? Question 4 is the one most likely to cause a real bug, because the code looks correct and works for exactly one second.
If you have a favourite "predict the output" snippet that catches people out, share it in the comments. If it fits, it could become the next question in the repo.
來源:DEV Community · dev.to