JavaScript runs on every machine in the world. Its bug landscape is shaped by three forces: a permissive type system that coerces freely, an asynchronous concurrency model that hides ordering, and a 25-year accumulation of language features layered on top of each other.

The bugs cluster around equality (`==` vs `===`), missing `await` on Promises that look fine until they don't, prototype pollution from `Object.assign`-style merges of untrusted JSON, and DOM-based XSS from any code path that lets user input flow into `innerHTML`.

Modern JS — strict mode, `===`, modules, async/await, classes, optional chaining — fixes a lot at the syntactic level. The semantic bugs (which Promise needs awaiting, which property assignment is a security hole) still need code review.

Common pitfalls

Floating promises

An async function call that isn't awaited or `.then`-chained becomes a silent error path. Linter rules catch it; review catches the rest.

Prototype pollution

`Object.assign({}, untrustedJson)` lets `__proto__` overwrite the global object prototype. Use `Object.create(null)` or a schema validator.

Truthiness traps

`if (x)` is false for `0`, `''`, `null`, `undefined`, `NaN`, and `false`. Strict checks (`x !== undefined`) prevent the wrong branch from taking.