Strange async code behavior
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
When asynchronous code behaves strangely, the bug is usually not randomness. It is usually a timing assumption that turned out to be false: a missing await, an operation running in parallel when you expected a sequence, or shared state being updated in an order you did not control.
Async Code Does Not Mean Sequential Code
In JavaScript, marking a function async does not make all statements in the program wait politely for one another. It only means that the function can pause at await points while the event loop continues doing other work.
This prints start, then after call, then done. That ordering surprises people who still expect the call to block like synchronous code.
Missing await Is The Classic Bug
A function that returns a promise will keep running in the background if you forget to await it.
Here userPromise is a promise object, not the resolved user. The fix is direct:
map With Async Functions Does Not Await Automatically
Another common surprise is Array.map with an async callback. It returns an array of promises, not an array of resolved values.
To resolve them, wrap the result with Promise.all.
That pattern makes the intended concurrency explicit.
Race Conditions Come From Shared State
Async bugs become harder when several operations read and write the same variable.
You might expect 2, but the result can be 1 because both functions read the same starting value before either writes back. The bug is not in await itself; it is in the shared-state design.
Debugging By Logging Boundaries
When behavior seems weird, add logs at async boundaries rather than only at the start and end of a request.
You want to observe when execution yields and resumes. That gives you a timeline instead of a guess.
Sequential And Parallel Code Need Different Shapes
If each operation depends on the previous one, use a loop with await inside.
If the operations are independent, use Promise.all.
A lot of "strange" async behavior is just the wrong control-flow shape for the intended order.
Common Pitfalls
The most common issue is forgetting await and then operating on a promise instead of a resolved value. Another is assuming that map, forEach, or filter will wait for async callbacks by themselves. Shared mutable state is also a major source of race conditions, especially when multiple requests update the same variable. Finally, code can look sequential even when it is not, so logging and tests should verify actual execution order instead of relying on intuition.
Summary
- Async code is usually strange only when the timing model is misunderstood.
- Missing
awaitoften explains why a value is a promise instead of data. - '
Promise.allis the standard way to wait for multiple concurrent operations.' - Shared mutable state creates race conditions even when each function looks correct by itself.
- Debugging async code gets easier when you log before and after await points.
Related reading
- Strategy to keep local cache see the same version of data in a distributed system
- Stream CopyToAsync never returns
- Strict serializability example clarification?
- Swagger async controller generation
- Strange delays in spark streaming
- Strange error nw_protocol_get_quic_image_block_invoke dlopen libquic failed
- Swift Async let with loop
- Swift closure async order of execution
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.