JavaScript Engine & Core Language
Deep dive into JS internals, event loop mechanics, closures, and modern TS type safety.
Deep dive into JS internals, event loop mechanics, closures, and modern TS type safety.
Data Types in JavaScript: Primitives vs. Objects
Primitives copy by value and objects copy by reference, so `b = a` duplicates a number but shares an object. That single distinction explains most surprise mutations in a codebase.
Variables: const, let & var (The Box Metaphor)
`const` prevents reassignment, not mutation: a `const` object's properties can still be changed. `let` is scoped to its block, while `var` is function-scoped and leaks out of loops.
Strings & Template Literals `${}`
Only backticks interpolate: `${user}` reads the variable, while the identical text in quotes prints literally. Strings are immutable, so every method returns a new one rather than editing in place.
Numbers, Arithmetic & The Modulo Operator (%)
Every JavaScript number is a 64-bit float, which is why `0.1 + 0.2` comes out as `0.30000000000000004`. `%` returns a remainder that keeps the sign of the left operand, so `-7 % 3` is `-1`.
Booleans & Truthy vs. Falsy Values
`==` converts types before comparing, which is how `0 == ""` ends up true. `===` compares type and value together, and that is why it is the default and `==` the deliberate exception.
Making Decisions: if, else & Ternaries
`if` branches on truthiness rather than equality, so `0`, `""`, `null` and `undefined` all take the else path. A ternary evaluates to a value, where `if` only performs a statement.
undefined vs. not defined in JavaScript
`undefined` means the name exists holding nothing; not defined means it was never declared and reading it throws. `typeof x` is the only check that survives the second case without crashing.
Functions: Inputs, Reusability & Return Values
A function with no `return` gives back `undefined`, not nothing. Arguments are copied in, so reassigning a parameter changes only the local copy unless it is an object, where the reference is shared.
How JS Runs Code: Execution Context & The Call Stack
The engine reserves memory for every declaration before it runs a single line, then executes. That two-phase order is why a `function` is callable above its definition while a `var` reads `undefined` there.
Hoisting: Why Variables & Functions Move to the Top
Hoisting moves no code anywhere. Memory is reserved during the first phase, so a `function` declaration is fully available early while a `var` merely exists holding `undefined` until its assignment runs.
Arrow Functions () => {} & Implicit Returns
An arrow function has no `this` of its own and borrows the surrounding scope's. That is exactly what makes it right for callbacks and wrong for object methods, where `this` should be the object.
The Scope Chain & Lexical Environment
A variable resolves by where the function was written, never where it was called. The lookup walks outward through each enclosing scope and throws `ReferenceError` only on reaching global empty-handed.
Block Scope {} & The Temporal Dead Zone (TDZ)
`let` and `const` are hoisted like `var`, but stay unreachable until their declaration runs. Touching one in that gap throws a `ReferenceError` instead of quietly handing back `undefined`.
Closures: Functions with Persistent Memories
A returned function keeps the variables it was created beside, even after the outer call has finished and its frame is gone. That is what makes a private `count` possible with no object at all.
Arrays & Zero-Based Indexing
Arrays index from 0, so the last element sits at `length - 1` and `arr[arr.length]` is always `undefined`. Nearly every off-by-one bug lives at that one boundary.
Objects & Key-Value Pairs
Object keys are always strings, so `obj[1]` and `obj["1"]` reach the same slot. `?.` stops a lookup at the first `null` or `undefined` rather than throwing on the property after it.
Transforming Arrays with .map()
`.map()` returns a new array of exactly the same length, one output per input. That length guarantee is the whole difference from `.filter()`, and why a map that drops items is the wrong tool.
Filtering Arrays with .filter()
`.filter()` keeps every element whose test returns truthy and hands back a new array, leaving the original untouched. A test that forgets to `return` filters everything out, because `undefined` is falsy.
Aggregating Arrays with .reduce()
`.reduce()` carries an accumulator across the array, and the initial value decides the shape of the result: `0` gives a number, `{}` an object, `[]` an array. Omit it and the first element becomes the seed.
Destructuring, Spread (...) & Rest Parameters
The same `...` gathers on the left and expands on the right: `[a, ...rest]` collects the remainder, `[...a, ...b]` combines two arrays. Both produce new values, so the originals are never mutated.
Shallow Copy vs. Deep Copy (structuredClone)
`{...obj}` copies only the top level, so nested objects stay shared and editing one edits both. `structuredClone()` walks the entire graph, which is why it is the one that actually detaches.
Grouping Data with Object.groupby() (ES2024)
`Object.groupBy()` buckets an array by whatever key the callback returns, replacing the usual `reduce` boilerplate. The result has a null prototype, so it carries no inherited methods to collide with keys.
Callbacks & Higher-Order Functions
Passing `fn` hands over the function; `fn()` hands over its result. That one pair of parentheses is the difference between scheduling work for later and having already run it.
The this Keyword & Execution Context
`this` is decided by how a function is called, not where it was defined: `user.greet()` binds `user`, while calling it alone binds `undefined`. Arrow functions inherit the enclosing scope.
The Event Loop: Microtasks vs. Macrotasks
The microtask queue drains completely before a single task runs, so a `.then()` always beats a `setTimeout(fn, 0)` queued before it. A microtask that queues more microtasks starves timers entirely.
Promises: Pending, Fulfilled & Rejected
A promise settles once and keeps its result, so a `.then()` attached afterwards still fires. `.catch()` handles a rejection from anywhere earlier in the chain, which is why one at the end usually suffices.
Clean Async Code with async/await
`await` pauses the function, not the thread; everything outside it keeps running. Two sequential awaits cost the sum of both waits, where `Promise.all` costs only the longer of the two.
Error Handling with try...catch
`catch` only sees errors thrown synchronously inside the `try`. A rejected promise or a callback that throws later escapes it entirely, which is why `await` inside the block is what makes async failures catchable.
ES Modules: import & export
A module's code runs once no matter how many files `import` it, and every importer shares that one instance. Named exports must match by name; a default export can be renamed freely at the import site.
JavaScript Proxy Objects & Traps
A Proxy sits between the code and the object, so `get` and `set` traps run on every property access. That is how observable state and validation work without modifying the original object at all.