Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
★

The cheat page

Not a 24th lesson — the night-before-the-interview skim of the other 23.

Nothing new gets taught here. Every row links back to the chapter that actually explains the why — this page exists purely so the what is skimmable in one pass, table after table, instead of re-reading three levels of chapters the night before something matters.

Coercion & comparison, at a glance

null == undefined 0 == false "0" == 0 [] == false NaN == NaN null == 0 "" == "0"

Full coercion table and the non-transitivity proof: Operators & flow.

console.log(0.1 + 0.2);            // what happens?
console.log(0.1 + 0.2 === 0.3);    // what happens?

0.30000000000000004, then false. Not a JS bug — IEEE 754 floats can't represent 0.1 or 0.2 exactly in binary, in any language that uses them. Never compare floats with ===; compare Math.abs(a - b) < Number.EPSILON instead, or work in integers (cents, not dollars) where exactness actually matters.

===Object.is()
NaN vs NaNfalsetrue
0 vs -0truefalse

Type checks, at a glance

ExpressionResult
typeof null"object" — a 25-year-old bug, permanent for compatibility
typeof undefined"undefined"
typeof NaN"number" — NaN IS a number, just not a useful one
typeof []"object" — use Array.isArray()
typeof function(){}, typeof class{}both "function"
typeof Symbol(), typeof 10n"symbol", "bigint"
[1,2] === [1,2]false — different references, same shape

Full type system: Types & values. Reference vs primitive: same chapter.

Scope, closures, this — the 30-second version

RankRuleTrigger
1newnew Fn()
2explicit.call() / .apply() / .bind()
3implicitobj.method()
4defaultbare fn() → undefined in strict mode

Arrows never bind their own this — they read it from where they're written. Full precedence proof (including new beating bind) and all five real closure uses: Scope & functions, properly.

Array & object methods — mutates, or doesn't?

Mutates the originalReturns a new one, leaves the original alone
push, pop, shift, unshiftslice, concat, map, filter
splice, sort, reverseflat, flatMap, spread [...arr]
Object.assign(target, …){ ...obj }, structuredClone(obj)
⚠ .map() skips holes in a sparse array Array(3).map(x => 1) is still [ <3 empty items> ], not [1, 1, 1] — Array(3) creates empty slots, not undefined values, and map/forEach/filter all skip holes entirely. Array.from({ length: 3 }) or Array(3).fill() first if you actually want real, mappable elements.

Full method tables: Objects & arrays (first half) and Objects deeply.

Async ordering — the one rule

Sync code runs first, always. Then the whole microtask queue drains (every .then(), every await continuation) — completely — before a single macrotask (setTimeout, a click) gets a turn.

CombinatorSettles when
Promise.allall fulfill, or the first rejection
Promise.allSettledeverything has settled — never rejects itself
Promise.racethe first to settle, win or lose
Promise.anythe first to fulfill — ignores earlier rejections

Full step-through demos: Setup & mental model. Sequential-vs-parallel await, retries, AbortController: Async, properly.

Classes & prototypes — the 30-second version

obj.hasOwnProperty("x")     // true only for OWN properties, never inherited ones
Object.getPrototypeOf(obj)  // the real link an instance follows
Fn.prototype                // what becomes that link for every "new Fn()"
class B extends A {
  constructor() { super(); }   // MUST run before "this" is usable
}

The 4 steps new actually performs, and #private being parser-enforced: Prototypes & OOP.

"Implement X" — the classic from-scratch asks

AskTaught in
debounce / throttleScope & functions, properly
curry / partial application / composeScope & functions, properly, Patterns & architecture
memoize / onceScope & functions, properly
your own EventEmitter / pub-subPatterns & architecture
deep clonestructuredClone() — Objects deeply
a concurrency-limited task queueAdvanced async
a custom iterable (Symbol.iterator)Metaprogramming
new from scratchPrototypes & OOP

The gotchas grid

{} + [] → 0 (statement position) [] + {} → "[object Object]" 1 < 2 < 3 → true 3 > 2 > 1 → false var in a loop + setTimeout → same value every time forEach can't be broken out of indexOf(NaN) is always -1 a bound "this" loses to new

Every one of these is explained, not just listed, in Operators & flow, Scope & functions, properly, and Objects & arrays.

How to actually use this page

  1. Don't start here. Every row above assumes the chapter behind its link has already been read once — this page is recall, not first exposure.
  2. Cover the "Result" column with your hand and predict it before checking. Being surprised by a row you've "read" before is the actual signal it needs another real pass, not just a re-skim.
  3. For the gotchas grid specifically: cover the chip past the arrow and say the reason out loud, not just the result — "why" is what an interviewer is actually asking for.
  4. If a whole section reads unfamiliar rather than "oh right" — go read that chapter properly. This page is a mirror, not a shortcut past the mirror.
←previousTesting↑ Cover