Concept Atlas
Break down the runtime concept behind the bug.
Each concept explains the mental model, common wrong assumption, real-world signals, debug questions, and the exact visual cases to practice.
Why output order changes
Synchronous code runs first, microtasks drain before timers, and Node adds extra queues like nextTick and check.
- - Console output differs from source order.
- - Tests need mysterious waits.
- - Cleanup runs later than expected.
- - setImmediate and setTimeout order feels inconsistent.
- - What runs on the current stack?
- - Which callbacks enter microtasks?
- - Which callbacks enter timers or Node-specific queues?
- - Is this browser behavior or Node behavior?
Why await did not wait
await pauses only the current async function. Missing await, async forEach, and unreturned promises let code continue too early.
- - Success UI appears before save finishes.
- - Bulk work logs complete too early.
- - A test finishes before setup completes.
- - The next .then receives undefined.
- - Is the promise awaited or returned?
- - Is async work inside forEach?
- - Does every .then return the value needed later?
- - Where are errors caught?
Why APIs become slower than expected
Independent I/O should usually start together. Sequential await turns total latency into the sum of every call.
- - Endpoint latency is close to call A + call B + call C.
- - Dashboard loads after the slowest plus every previous request.
- - Serverless function duration is higher than any single dependency.
- - Which requests truly depend on previous results?
- - Can independent promises start before the first await?
- - Should failure be all-or-nothing or partial?
- - Do timeouts cancel the slow work?
Why the app freezes
Synchronous CPU work blocks the main thread or Node event loop, delaying input, paint, timers, and callbacks.
- - Clicks feel ignored.
- - Timers fire late.
- - Animations freeze.
- - One request delays unrelated Node callbacks.
- - Is there a long task over 50ms?
- - Is JSON parsing, sorting, or transforming huge data?
- - Can work be chunked?
- - Should a worker thread handle it?
Why memory keeps growing
Garbage collection frees unreachable objects. Timers, listeners, caches, closures, and streams can keep objects reachable.
- - Heap grows after repeated navigation.
- - Listener count increases.
- - Polling continues after a component disappears.
- - Large files spike memory.
- - What reference keeps this object alive?
- - Is there matching cleanup?
- - Is the cache bounded?
- - Does stream failure close resources?
Why Node gets slow under async load
Node has one main JS thread plus limited backend capacity. fs, crypto, zlib, and DNS-like work can queue behind each other.
- - Crypto slows file work.
- - DNS or outbound requests look delayed.
- - Compression bursts hurt unrelated endpoints.
- - p95 latency spikes under batch jobs.
- - Which APIs use worker-pool capacity?
- - Is Promise.all starting too much at once?
- - Can concurrency be limited?
- - Should heavy work move out of request paths?
Why streams hang or use too much memory
Streams are chunked data flow. Backpressure and error cleanup keep the fast side from overwhelming the slow side.
- - Uploads hang.
- - Large downloads use too much memory.
- - A failed transform leaves a socket open.
- - write buffers keep growing.
- - Is backpressure respected?
- - Does every stream have error handling?
- - Would pipeline simplify cleanup?
- - Is the whole file being read into memory?
Why one failure breaks everything
Promise combinators encode failure behavior. all fails fast, allSettled keeps outcomes, any waits for first success, and race settles first.
- - One widget blanks a whole dashboard.
- - Batch uploads look fully failed.
- - A fast failed mirror beats a slower success.
- - Timeouts do not cancel slow work.
- - Should one failure stop everything?
- - Do you need every result status?
- - Should first failure count as winner?
- - Does timeout cancel the slow operation?