Idioms & Common Gotchas - Write It Like a Local, Dodge the Traps
You can now write JavaScript that works. This phase covers writing it the way experienced developers do - and dodging the handful of traps that have confused every JavaScript programmer who ever lived. (Genuinely - the ones in the cheat-card below have wasted millions of collective hours. You're about to skip that.)
Two halves: idioms - modern syntax that makes code shorter and clearer, seen in every codebase - then a scannable gotcha cheat-card, surprises named before they bite so you recognize them instead of staring at the screen.
Modern idioms - the way it's written today
Destructuring - unpack in one line
Pulling values out of an object or array straight into named variables, instead of one assignment per field.
const = ;
const = user; // object destructuring
const = ; // array destructuring
;
Ada admin 10
What just happened: const { name, role } = user created two variables named after the object's keys in one line - same as const name = user.name; const role = user.role; but shorter, and standard. You'll see it constantly in function parameters too: function greet({ name }) { ... }.
Spread & rest - ... does two jobs
The ... operator either spreads a collection out into pieces, or gathers loose pieces into one - depending on where you use it.
const = ;
const = ; // spread: copy a's items into a new array
const = ; // spread: copy + override a field
;
[ 1, 2, 3, 4 ] 6
What just happened: [...a, 3, 4] spread a's elements into a brand-new array (copying without mutating). { ...user, role: "user" } copied user and overrode one field. In sum(...nums), the same ... did the opposite - gathered every argument into an array. Same symbol, mirror-image jobs.
Optional chaining ?. and nullish ??
?. safely reads a property that might not exist; ?? supplies a fallback only when something is null or undefined.
const = ;
; // "Ada"
; // undefined - no crash
; // 0 - fallback
What just happened: data.order?.total would normally crash (data.order is undefined, and you can't read .total of undefined), but ?. short-circuits to undefined instead. Then ?? 0 supplies a default - together they replace whole towers of if (data && data.order && ...) checks.
⚠️ Gotcha: use ??, not ||, for defaults - when 0 or "" are valid. || falls back on any falsy value, so count || 10 gives 10 even when count is a legitimate 0. ?? only falls back on null/undefined, correctly keeping the 0. Reach for ?? whenever zero or empty-string is a real value.
Array methods over manual loops
map, filter, reduce, find, and friends express what you want done to a list, rather than a manual for loop spelling out how.
const = ;
const = ; // transform each
const = ; // keep some
const = ; // combine to one
;
[ 2, 4, 6, 8 ] [ 2, 4 ] 10
What just happened: Each method takes a small function and applies it across the array, returning a new array (or value) without an index or counter to manage. The code reads like a sentence - "map each to double" - and you can't fat-finger an off-by-one. The default style for lists.
Modules over globals
Sharing code through explicit import/export (Phase 5) rather than dumping everything onto shared global variables.
A global variable is reachable - and editable - from anywhere, so any file can quietly break any other. Modules make sharing intentional: a file exports what it means to share, and importers state what they depend on, so when something changes you can trace who's affected. Prefer the import; avoid the global.
💡 The umbrella idiom: prefer the form that makes intent explicit and prevents silent mistakes. Destructuring names what you took,
??says exactly when to fall back, modules declare what's shared. Clarity over cleverness.
The gotcha cheat-card
Hit something baffling? Find the symptom here, then read the note below. These trap everyone - recognizing them is the whole battle.
| The trap | What bites you | The fix |
|---|---|---|
== vs === |
0 == "" is true; 1 == "1" is true |
Always use === (and !==) |
this binding |
this is undefined/wrong inside a callback |
Use arrow functions; they keep the outer this |
| Hoisting | A function works before its line; let/const don't |
Declare before use; prefer const |
NaN |
NaN === NaN is false |
Test with Number.isNaN(x) |
| Floating point | 0.1 + 0.2 !== 0.3 |
Round, or compare with a tolerance |
| Shared references | Copying an object copies the pointer, not the data | Copy with { ...obj } / [...arr] |
| Truthy/falsy | if (count) skips a real 0 |
Check explicitly: if (count > 0) |
The why behind each:
== vs ===
== performs type coercion, converting operands to a common type before comparing, producing famous nonsense like 0 == "" and false == "0" both being true.
; // true (coerced - surprising)
; // false (no coercion - sane)
What just happened: == quietly converted both sides until they matched; === compared type and value, so a number and a string are never equal. Always use === - no surprises, and ESLint will nag you if you slip.
this binding
this doesn't mean "the current object" as in some languages - it depends on how a function is called, and inside a plain-function callback it often isn't what you expect.
const = ;
What just happened: In startBroken, the plain function callback got its own this (not counter), so this.count++ fails silently. The arrow function in startFixed has no own this - it borrows the surrounding one, which is counter. Rule of thumb: arrow functions for callbacks, and the problem mostly disappears.
Hoisting
function declarations are hoisted - moved to the top of their scope - so they work before the line they're written on. let/const are not usable before their declaration.
; // works - function declarations are hoisted
; // ReferenceError - can't use before declaration
const = 5;
What just happened: greet ran before its definition since function declarations are pulled up. const x was not usable early - it throws until its line runs. The clean habit: declare before use and hoisting stops mattering.
NaN
NaN ("Not a Number") is the result of invalid math - the only value in JavaScript not equal to itself.
const = ; // NaN
; // false (!)
; // true
What just happened: NaN === NaN is false by design, so the obvious check silently fails - use Number.isNaN(x) instead. Seeing NaN in your output usually means a string-to-number conversion went wrong upstream.
Floating point
Numbers are stored in binary floating point, which can't represent some decimals exactly, so arithmetic has tiny rounding errors.
; // 0.30000000000000004
; // false
What just happened: 0.1 and 0.2 have no exact binary form, so their sum is a hair off - not a JavaScript bug, but how floating point works in nearly every language. For money, work in integer cents; for comparisons, round or check Math.abs(a - b) < 0.0001.
Shared references
Objects and arrays are held by reference. Assigning one to a new variable doesn't copy the data - both names point at the same object, so a change through one is visible through the other.
const = ;
const = a; // NOT a copy - same object
b. = 99;
; // 99 - `a` changed too
What just happened: b = a copied the reference, not the contents, so mutating through b mutated the one shared object. For an independent copy, spread it: const b = { ...a }. Behind countless "why did my other variable change?!" bugs.
Truthy / falsy
In a condition, non-boolean values are coerced to true/false. The falsy values: false, 0, "", null, undefined, NaN. Everything else is truthy.
const = 0;
; // never runs - 0 is falsy!
; // correct
What just happened: if (count) treated a real 0 as false and skipped the block - a classic bug when 0 is a valid value. When you mean "exists," check explicitly (count > 0, value != null) rather than trusting truthiness.
Recap
- Idioms: destructuring unpacks,
...spreads/gathers,?.reads safely,??defaults on null/undefined, array methods beat manual loops, modules beat globals. - Always
===-==coerces types and lies. thisdepends on how a function is called; arrow functions keep the outerthis.NaNisn't equal to itself (Number.isNaN); floats are imprecise (0.1 + 0.2); objects copy by reference (spread to clone).- Truthy/falsy treats
0and""as false - check existence explicitly when valid.
← Phase 8: The Ecosystem & Tooling · Guide overview · Phase 10: Scope, Closures & Hoisting →
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 2 questions
1. ?. (optional chaining) and ?? (nullish coalescing)...
2. Inside a plain-function callback, the keyword this...