JavaScript runs almost everywhere and refuses almost nothing, and those two facts explain most of the pain. It converts one type into another rather than stop; it decides what this means when you call a function, not when you write it; and it gives you one thread to do all the waiting on. None of it is magic: it is a small set of rules applied more literally than people expect. This is the shape of those rules — what the language actually does, where it will not warn you, and the habits that keep you out of the traps.
Each chapter opens with the short version. Tap one to read the detail.
Bindings, values, and what gets hoisted
~2 min
Use const by default and let when you must reassign; var is function-scoped and starts life holding undefined, which is why it reads as broken. And const fixes the binding, never the value — the properties of a constant object are still writable.
Coercion: the rules behind the jokes
~2 min
== converts before comparing and === does not, so use === and stop thinking about it. The conversions that actually cost you are + preferring string concatenation, and || treating 0 and the empty string as missing where ?? does not.
Functions, this, and closures
~2 min
this is decided by the call, not by where the function was written, so a method pulled off its object loses it. Arrow functions have no this of their own — which is exactly why they are right for callbacks and wrong for methods.
Errors, and the block that outranks them
~2 min
Any value can be thrown, so a catch must never assume it received an Error. And finally outranks everything above it: a return there replaces the value you were returning and silently discards the exception you were throwing.
Looping, and the protocol underneath it
~2 min
for...in gives you keys, including inherited ones, and for...of gives you values — which is why for...in over an array is a bug waiting for somebody to add a property. forEach cannot be stopped: return ends one call and nothing more.
Objects: keys, copies, and interception
~2 min
Keys are only strings or symbols, so anything else is stringified and every object used as a key collapses to the same one. Spread, Object.assign and Object.freeze all stop one level down — nested objects stay shared and stay writable.
Prototypes, and classes as the syntax over them
~2 min
Every object has a prototype, and a failed lookup walks that chain — but assignment never does, so writing always creates an own property that shadows the inherited one. class is the readable syntax for this: methods are shared on the prototype, fields are per instance.
Numbers: one type, finite precision
~2 min
There is one number type, a 64-bit float, so 0.1 + 0.2 is not 0.3 and integers stop being exact above about nine quadrillion. NaN is a number that never equals itself, and toFixed hands back a string.
Text: code units, patterns, and locale
~2 min
A string is a sequence of 16-bit code units, not characters, so an emoji has a length of 2 and index-based slicing can cut one in half. A regular expression with the global flag remembers where it stopped, which makes a shared one silently skip matches.
Arrays, maps, sets, and buffers
~2 min
sort() with no comparator converts everything to strings, so a list of numbers comes back in dictionary order. Reach for a Map once keys stop being simple strings — and remember it matches object keys by identity, not by shape.
One thread: promises, await, and the event loop
~2 min
There is one thread, so nothing else runs while your code does. Promise callbacks are microtasks and run before any timer however short, and await inside a loop turns independent work serial — the most common reason async code is slow.
Modules: static, live, and evaluated once
~2 min
Imports are resolved before any code runs, so you cannot import conditionally — that is what import() is for. What you import is a live read-only view of the exporter's binding, and each module body runs exactly once no matter how many files import it.
The boundary: JSON and dates
~2 min
A round trip through JSON is lossy by design: undefined and functions vanish from objects, become null inside arrays, and a date comes back as a string. Date parsing has one rule everybody trips on — a date-only string is read as universal time, a date-time string without an offset as local.
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.