BugHunt

Async bugs and race conditions in JavaScript

A missing await, results in the wrong order, and updates that overwrite each other under load.

14 free challenges14 JavaScript

What is async and race condition?

Code that assumes work has finished when it has not. The symptom is a Promise where a value should be, results arriving in an unexpected order, or state that is correct with one user and wrong with several.

Why it happens

An async function always returns a promise, so forgetting a single await hands the promise itself onward. `return somePromise` inside a try block escapes before it can reject, so the catch never runs. And every await is a point where other code can run — if you read a value, await, then write it back, anything that ran in the gap is silently overwritten.

How to recognise it

  • "[object Promise]" or typeof 'object' where a value was expected.
  • It works with one item and breaks with several.
  • Output order changes between runs.
  • A try/catch around async work never catches anything.

Errors and symptoms this causes

  • [object Promise] in output
  • value is undefined after await
  • results in wrong order
  • try/catch does not catch async error
  • UnhandledPromiseRejection

How to fix it

Await everything that returns a promise, and use Promise.all when you need every result rather than the first. Never hold a copy of shared state across an await — read and write without yielding in between. Test concurrency with concurrent input; a sequential test cannot find a race.

Practise async and race conditions

Working code with one bug in it. Find it, fix it in the browser, and see the explanation. No account needed.

javascriptEasy
Arrow body swallows the returned name

getName(id) should resolve to the loaded user's name, so getName(1) gives "user1".

Async race condition

javascriptHard
Doubled value returned before it's ready

delayedDouble(n) should asynchronously resolve to n doubled, e.g. delayedDouble(5) eventually resolves to 10.

Async race condition

javascriptHard
Overbooking when everyone checks at once

bookSeats(n) handles n concurrent booking requests for a single remaining seat and returns how many bookings succeeded. It should never exceed 1.

Async race condition

javascriptHard
Cache stampede loads the config twice

countLoads(n) makes n concurrent calls to a cached loader and returns how many times the expensive load actually ran. It should always be 1.

Async race condition

javascriptHard
Concurrent tally loses most of the votes

tallyVotes(votes) should add up every vote, so tallyVotes([1, 2, 3, 4]) returns 10.

Async race condition

javascriptHard
try/catch never catches the async failure

safeDivide(a, b) should return a / b, or -1 when the division fails. safeDivide(10, 0) should return -1.

Async race condition

javascriptHard
Async reduce builds a string of Promises

sumAsync(nums) should add up values fetched asynchronously, so sumAsync([1, 2, 3]) returns 6.

Async race condition

javascriptHard
Results arrive in whatever order they finish

labelAll(items) should return the names in the same order they were passed in, regardless of how long each one takes.

Async race condition

javascriptHard
forEach finishes before the work does

doubleAll(nums) should resolve to a list with every number doubled.

Async race condition

javascriptMedium
Missing await leaves a Promise in the array

collectNames(ids) should resolve every name and report the count and the type of the first entry, so collectNames([1,2]) gives "2-string".

Async race condition

javascriptMedium
Only the fastest request comes back

loadAll(ids) should load every id and return all the results as an array, so loadAll([1, 2, 3]) returns [10, 20, 30].

Async race condition

javascriptMedium
Value returned from setTimeout goes nowhere

doubleLater(value) should resolve to double the value after a short delay, so doubleLater(4) gives 8.

Async race condition

javascriptMedium
Missing await turns the total into text

sumDelayed(nums) should add up numbers that each arrive asynchronously, resolving to the total.

Async race condition

javascriptMedium
map returns promises instead of values

wordLengths(words) should resolve to a list of each word's length.

Async race condition

Other bug patterns