diff --git a/docs/topics/javascript.md b/docs/topics/javascript.md index d762b05..054ea1c 100644 --- a/docs/topics/javascript.md +++ b/docs/topics/javascript.md @@ -6,66 +6,66 @@ What experienced JavaScript engineers forget before an interview, grouped by sub ### Microtasks vs macrotasks -- The **microtask queue** (promise reactions, `queueMicrotask`, `MutationObserver`) drains **completely** whenever the call stack empties. -- The task (macrotask) queue (`setTimeout`, I/O, UI events) runs **one task per loop iteration**, then microtasks drain again. +- The [microtask queue](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) (promise reactions, [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/Window/queueMicrotask), [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)) drains completely whenever the call stack empties. +- The [task (macrotask) queue](https://html.spec.whatwg.org/multipage/webappapis.html#event-loops) ([`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout), I/O, UI events) runs one task per loop iteration, then microtasks drain again. - So `Promise.resolve().then(f)` always beats `setTimeout(f, 0)`; a microtask that keeps queuing microtasks starves rendering and tasks. ### Rendering in the loop -- **`requestAnimationFrame`** callbacks, style, layout, and paint run between tasks, at most once per frame (~16.7 ms at 60 Hz), after microtasks drain. -- A long task or microtask chain delays the next frame — this is what INP measures (see [frontend.md](frontend.md)). +- [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame) callbacks, style, layout, and paint run between tasks, at most once per frame (~16.7 ms at 60 Hz), after microtasks drain. +- A [long task](https://developer.mozilla.org/en-US/docs/Glossary/Long_task) or microtask chain delays the next frame — this is what [INP](https://web.dev/articles/inp) measures (see [frontend.md](frontend.md)). ## Scope and closures ### Hoisting and the TDZ -- `var` is hoisted and reads as `undefined` before its line; a `function` declaration is callable before its line. -- `let`/`const`/`class` are hoisted but sit in the **temporal dead zone** — access before the declaration throws `ReferenceError`. +- `var` is [hoisted](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) and reads as `undefined` before its line; a `function` declaration is callable before its line. +- `let`/`const`/`class` are hoisted but sit in the [temporal dead zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz) — access before the declaration throws `ReferenceError`. ### Closures in loops -- `for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))` logs `3,3,3` — one function-scoped `i`. -- `let` creates a **fresh binding per iteration**, printing `0,1,2`. +- `for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))` logs `3,3,3` — one function-scoped `i` shared by every [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#creating_closures_in_loops_a_common_mistake). +- `let` creates a [fresh binding per iteration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for#lexical_declarations_in_the_initialization_block), printing `0,1,2`. ## `this` and prototypes ### `this` binding rules -- Precedence: **`new`** > explicit `call`/`apply`/`bind` > implicit (`obj.method()`) > default (`undefined` in strict mode, global otherwise). -- **Arrow functions** have no own `this`; they capture it lexically. +- Precedence: `new` > explicit [`call`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)/[`apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply)/[`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) > implicit (`obj.method()`) > default (`undefined` in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode), global otherwise). +- [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) have no own [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this); they capture it lexically. - Passing `obj.method` as a bare callback (`setTimeout(obj.method)`) loses `this`. ### Prototypes and classes -- Property lookup walks the **`[[Prototype]]` chain** until found or `null`; `class` methods live on `C.prototype`. -- Unlike constructor functions, classes run in strict mode, have a **TDZ**, and throw if called without `new`. -- **`#private` fields** are engine-enforced: `obj.#x` outside the class is a syntax error. +- Property lookup walks the [`[[Prototype]]` chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain) until found or `null`; `class` methods live on `C.prototype`. +- Unlike constructor functions, [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) run in strict mode, have a TDZ, and throw if called without `new`. +- [`#private` fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements) are engine-enforced: `obj.#x` outside the class is a syntax error. ## Coercion and equality ### `==`, `===`, `Object.is` -- `==` coerces: `'' == 0` and `null == undefined` are `true`, but `null == 0` is `false`; `===` never coerces. -- `NaN !== NaN`; **`Object.is`** treats `NaN` as equal to itself and distinguishes `+0` from `-0`. +- [`==` coerces](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness#loose_equality_using): `'' == 0` and `null == undefined` are `true`, but `null == 0` is `false`; `===` never coerces. +- `NaN !== NaN`; [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) treats `NaN` as equal to itself and distinguishes `+0` from `-0`. ### Type-check quirks -- `typeof null === 'object'` — a historical bug kept for compatibility. -- Default `Array.prototype.sort()` compares **as strings**: `[10, 2, 1].sort()` → `[1, 10, 2]`; pass `(a, b) => a - b`. +- [`typeof null === 'object'`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null) — a historical bug kept for compatibility. +- Default [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) compares as strings: `[10, 2, 1].sort()` → `[1, 10, 2]`; pass `(a, b) => a - b`. ## Numbers ### IEEE 754 doubles -- Every `number` is a 64-bit double: `0.1 + 0.2 !== 0.3`; compare with a tolerance or use integers (cents). -- Integers are exact only up to **`Number.MAX_SAFE_INTEGER` = 2⁵³ − 1**; IDs beyond that (Snowflake, tweet IDs) must travel as strings or `BigInt`. -- Bitwise operators on numbers truncate to **32-bit** signed integers (`>>>` returns unsigned); on `BigInt` they are arbitrary-precision. -- `Math.sumPrecise(iterable)` (ES2026) adds floats without accumulating rounding error, unlike a naive `reduce`. +- Every `number` is a [64-bit double](https://en.wikipedia.org/wiki/Double-precision_floating-point_format): `0.1 + 0.2 !== 0.3`; compare with a tolerance or use integers (cents). +- Integers are exact only up to [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) = 2⁵³ − 1; IDs beyond that ([Snowflake](https://en.wikipedia.org/wiki/Snowflake_ID), tweet IDs) must travel as strings or [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). +- Bitwise operators on numbers [truncate to 32-bit](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#fixed-width_number_conversion) signed integers (`>>>` returns unsigned); on `BigInt` they are arbitrary-precision. +- [`Math.sumPrecise(iterable)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sumPrecise) (ES2026) adds floats without accumulating rounding error, unlike a naive `reduce`. ### Typed arrays -- **Typed arrays** are fixed-type views over an `ArrayBuffer` — unboxed numeric storage; several views can share one buffer. -- `SharedArrayBuffer` + `Atomics` share memory between workers. +- [Typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays) are fixed-type views over an [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) — unboxed numeric storage; several views can share one buffer. +- [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) + [`Atomics`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics) share memory between workers. ## Async @@ -73,134 +73,134 @@ What experienced JavaScript engineers forget before an interview, grouped by sub | Combinator | Settles when | Use when | |---|---|---| -| `Promise.all` | any rejects, or all fulfill | need every result, fail fast | -| `Promise.allSettled` | all settle (input rejections don't reject it), tagged fulfilled/rejected | need every outcome | -| `Promise.race` | first settles (either way) | timeouts, first response wins | -| `Promise.any` | first fulfills, or all reject (`AggregateError`) | first success wins | +| [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) | any rejects, or all fulfill | need every result, fail fast | +| [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) | all settle (input rejections don't reject it), tagged fulfilled/rejected | need every outcome | +| [`Promise.race`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) | first settles (either way) | timeouts, first response wins | +| [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) | first fulfills, or all reject ([`AggregateError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError)) | first success wins | ### async/await ordering -- The `new Promise` executor runs **synchronously**; only the `.then` callbacks are deferred. -- Code after `await` always resumes as a microtask, even when the awaited value is already resolved or isn't a promise. -- Inside `try`, **`return promise`** skips the `catch` when it rejects; write `return await promise`. -- An `async` function always returns a new promise, so a `throw` inside it becomes a rejection, not a synchronous exception. +- The [`new Promise` executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) runs synchronously; only the `.then` callbacks are deferred. +- Code after [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) always resumes as a microtask, even when the awaited value is already resolved or isn't a promise. +- Inside `try`, `return promise` skips the `catch` when it rejects; write `return await promise`. +- An [`async` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) always returns a new promise, so a `throw` inside it becomes a rejection, not a synchronous exception. ### Sequential vs parallel await -- `for (const x of xs) await f(x)` runs **serially**: each call starts after the previous settles; start all calls first, then `await Promise.all([...])` so they overlap. -- An unhandled rejection doesn't throw synchronously; Node **crashes the process on it by default since Node 15**. +- `for (const x of xs) await f(x)` runs serially: each call starts after the previous settles; start all calls first, then `await Promise.all([...])` so they overlap. +- An unhandled rejection doesn't throw synchronously; Node [crashes the process on it by default](https://nodejs.org/api/cli.html#--unhandled-rejectionsmode) since Node 15. ### Cancellation and async iteration -- **`AbortController`** cancels `fetch` and other abortable APIs via a shared `AbortSignal`; `AbortSignal.timeout(ms)` builds a deadline. -- **`for await...of`** consumes async generators and any object with `Symbol.asyncIterator`. +- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) cancels `fetch` and other abortable APIs via a shared [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal); [`AbortSignal.timeout(ms)`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) builds a deadline. +- [`for await...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) consumes async generators and any object with [`Symbol.asyncIterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator). ### fetch semantics -- `fetch` rejects only on network failure (DNS, refused connection, CORS, abort); HTTP 4xx/5xx **resolve** with `res.ok === false`, so check it. -- The body is a stream and can be read **once**; call `res.clone()` before reading it twice. +- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) rejects only when the request can't be made (network error, CORS, invalid URL or options, abort); HTTP 4xx/5xx resolve with [`res.ok`](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok) `=== false`, so check it. +- The body is a stream and can be read once; call [`res.clone()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/clone) before reading it twice. - There is no default timeout: pass `signal: AbortSignal.timeout(ms)`. -- Cookies go only to the same origin by default (`credentials: 'same-origin'`); cross-origin requests need `'include'` plus CORS credential headers. +- Cookies go only to the same origin by default ([`credentials: 'same-origin'`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#credentials)); cross-origin requests need `'include'` plus [CORS credential headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#requests_with_credentials). ## Modules ### CommonJS vs ESM -- CommonJS `require` loads **synchronously** at runtime and can be called conditionally; a destructured import is a snapshot, not a live binding. -- ESM is statically analyzed — enabling tree shaking and **live bindings** — loads asynchronously, and allows **top-level `await`**. +- [CommonJS](https://nodejs.org/api/modules.html) `require` loads synchronously at runtime and can be called conditionally; a destructured import is a snapshot, not a live binding. +- [ESM](https://nodejs.org/api/esm.html) is statically analyzed — enabling [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) and [live bindings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#imported_values_can_only_be_modified_by_the_exporter) — loads asynchronously, and allows [top-level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await). ### Node interop and the dual-package hazard -- **`require(esm)`** works for synchronous ES modules (no top-level `await`) without a flag **since Node 22.12 / 20.19**. -- **Dual-package hazard**: a package shipping CJS and ESM builds can load twice under different identities, breaking `instanceof` and singletons. +- [`require(esm)`](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) works for synchronous ES modules (no top-level `await`) without a flag since Node 22.12 / 20.19. +- [Dual-package hazard](https://nodejs.org/api/packages.html#dual-commonjses-module-packages): a package shipping CJS and ESM builds can load twice under different identities, breaking `instanceof` and singletons. ## Memory ### Garbage collection and leaks -- V8's collector is **generational**: a fast scavenger for the young generation (most objects die young), mark-compact for the old generation. -- Common leaks: **detached DOM nodes** still referenced from JS, forgotten listeners and timers, unbounded caches. -- A closure keeps its referenced outer variables alive, so a long-lived listener can retain a large object it barely uses. +- V8's collector is [generational](https://v8.dev/blog/trash-talk): a fast scavenger for the young generation (most objects die young), mark-compact for the old generation. +- Common leaks: [detached DOM nodes](https://developer.chrome.com/docs/devtools/memory-problems#discover_detached_dom_tree_memory_leaks_with_heap_snapshots) still referenced from JS, forgotten listeners and timers, unbounded caches. +- A [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures) keeps its referenced outer variables alive, so a long-lived listener can retain a large object it barely uses. ### Hidden classes and inline caches -- V8 gives objects created with the same properties in the same order a shared **hidden class** (shape); adding a property transitions to a new one. -- Each property-access site caches the shapes it has seen: monomorphic (one) is fastest, polymorphic up to 4, then **megamorphic** (a generic slow lookup). -- Initialize every field in the constructor in a fixed order; `delete` on a hot object can drop it into slow dictionary mode. -- Arrays track element kinds too: mixing integers, doubles, and holes (`new Array(n)`) downgrades them for good. +- V8 gives objects created with the same properties in the same order a shared [hidden class](https://v8.dev/docs/hidden-classes) (shape); adding a property transitions to a new one. +- Each property-access site [caches the shapes](https://mathiasbynens.be/notes/shapes-ics) it has seen: monomorphic (one) is fastest, polymorphic up to 4, then megamorphic (a generic slow lookup). +- Initialize every field in the constructor in a fixed order; `delete` on a hot object can drop it into slow [dictionary mode](https://v8.dev/blog/fast-properties). +- Arrays track [element kinds](https://v8.dev/blog/elements-kinds) too: mixing integers, doubles, and holes (`new Array(n)`) downgrades them for good — except that `Array.prototype.fill` can repack a holey array (since February 2025). ### Weak references -- **`WeakMap`/`WeakSet`** keys don't keep objects alive — good for per-object metadata and caches. -- **`WeakRef`** weakly holds one object; **`FinalizationRegistry`** callbacks may run late or never — cleanup hints only, never correctness. +- [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)/[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) keys don't keep objects alive — good for per-object metadata and caches. +- [`WeakRef`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef) weakly holds one object; [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) callbacks may run late or never — cleanup hints only, never correctness. ### Deep cloning -- **`structuredClone`** handles cycles, `Map`, `Set`, `Date`, and typed arrays, but not functions or DOM nodes. -- `JSON.parse(JSON.stringify(x))` drops `undefined`/functions/symbols, turns `Date` into a string, throws on `BigInt` and cycles. +- [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) handles cycles, `Map`, `Set`, `Date`, and typed arrays, but not functions or DOM nodes. +- `JSON.parse(JSON.stringify(x))` ([`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)) drops `undefined`/functions/symbols, turns `Date` into a string, throws on `BigInt` and cycles. ## Metaprogramming and iteration ### Proxy and Reflect -- A **`Proxy`** intercepts operations (get, set, has, delete, apply) on a target — the basis of Vue's reactivity and MobX. -- **`Reflect`** exposes the default behavior of each trap, so handlers can forward with `Reflect.get(target, key, receiver)`. +- A [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) intercepts operations (get, set, has, delete, apply) on a target — the basis of [Vue's reactivity](https://vuejs.org/guide/extras/reactivity-in-depth.html) and MobX. +- [`Reflect`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) exposes the default behavior of each trap, so handlers can forward with `Reflect.get(target, key, receiver)`. ### Iterators and generators -- An object is iterable if it has **`[Symbol.iterator]()`** returning `{ next() → { value, done } }`; `for...of`, spread, and destructuring use it. -- Generators (`function*`) are lazy, pause at `yield`, and can receive values via `next(v)`; async generators power `for await`. -- **Iterator helpers** (`.map`, `.filter`, `.take`, `.drop` on iterators, ES2025) are lazy, unlike the array methods. +- An object is [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) if it has `[Symbol.iterator]()` returning `{ next() → { value, done } }`; `for...of`, spread, and destructuring use it. +- [Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) (`function*`) are lazy, pause at `yield`, and can receive values via `next(v)`; async generators power `for await`. +- [Iterator helpers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#iterator_helper_objects) (`.map`, `.filter`, `.take`, `.drop` on iterators, ES2025) are lazy, unlike the array methods. ## Node.js runtime ### Node event loop phases -- Phases: **timers → pending callbacks → poll (I/O) → check (`setImmediate`) → close**. -- **Since Node 20 (libuv 1.45)**, timers run after poll in each iteration instead of before it (plus once when the loop starts); this can shift timer vs `setImmediate` ordering. -- After each callback, the `process.nextTick` queue drains first, then promise microtasks. -- `setTimeout(f, 0)` vs `setImmediate(f)` order is **nondeterministic** in the main module; inside an I/O callback `setImmediate` always runs first. +- [Phases](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#phases-overview): timers → pending callbacks → poll (I/O) → check (`setImmediate`) → close. +- Since Node 20 ([libuv 1.45](https://github.com/libuv/libuv/releases/tag/v1.45.0)), timers run after poll in each iteration instead of before it (plus once when the loop starts); this can shift timer vs `setImmediate` ordering. +- After each callback, the [`process.nextTick`](https://nodejs.org/api/process.html#processnexttickcallback-args) queue drains first, then promise microtasks. +- `setTimeout(f, 0)` vs [`setImmediate(f)`](https://nodejs.org/api/timers.html#setimmediatecallback-args) order is [nondeterministic](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#setimmediate-vs-settimeout) in the main module; inside an I/O callback `setImmediate` always runs first. ### libuv thread pool -- Network sockets use epoll/kqueue, but **`fs`**, `dns.lookup`, async `crypto` (`pbkdf2`, `scrypt`), and `zlib` run on the libuv **thread pool**. -- The pool has **4 threads** by default (`UV_THREADPOOL_SIZE`), so a few slow DNS lookups or hashes can stall all file I/O. +- Network sockets use [epoll](https://en.wikipedia.org/wiki/Epoll)/[kqueue](https://en.wikipedia.org/wiki/Kqueue), but `fs`, [`dns.lookup`](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback), async `crypto` (`pbkdf2`, `scrypt`), and `zlib` run on the libuv [thread pool](https://docs.libuv.org/en/v1.x/threadpool.html). +- The pool has 4 threads by default ([`UV_THREADPOOL_SIZE`](https://nodejs.org/api/cli.html#uv_threadpool_sizesize)), so a few slow DNS lookups or hashes can stall all file I/O. ### Stream backpressure -- **`write()` returns `false`** once the buffer passes `highWaterMark`; stop writing until the `'drain'` event. -- **`stream.pipeline()`** handles backpressure, errors, and cleanup of every stream in the chain — `.pipe()` doesn't forward errors. +- [`write()` returns `false`](https://nodejs.org/api/stream.html#writablewritechunk-encoding-callback) once the buffer passes [`highWaterMark`](https://nodejs.org/api/stream.html#buffering); stop writing until the [`'drain'`](https://nodejs.org/api/stream.html#event-drain) event. +- [`stream.pipeline()`](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-callback) handles [backpressure](https://nodejs.org/en/learn/modules/backpressuring-in-streams), errors, and cleanup of every stream in the chain — `.pipe()` doesn't forward errors. ### Worker threads and cluster -- **`worker_threads`**: threads in one process with separate V8 isolates, for CPU-bound JS; share memory through `SharedArrayBuffer`. -- **`cluster`**: forks processes sharing one server port to use all cores — in containers, usually replaced by more replicas. +- [`worker_threads`](https://nodejs.org/api/worker_threads.html): threads in one process with separate V8 isolates, for CPU-bound JS; share memory through `SharedArrayBuffer`. +- [`cluster`](https://nodejs.org/api/cluster.html): forks processes sharing one server port to use all cores — in containers, usually replaced by more replicas. ### AsyncLocalStorage -- **`AsyncLocalStorage`** carries request-scoped context (request ID, trace, user) through every async call without passing it as an argument. -- The basis of Node APM tracing and per-request logging. +- [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) carries request-scoped context (request ID, trace, user) through every async call without passing it as an argument. +- The basis of Node APM tracing (e.g. [OpenTelemetry](https://opentelemetry.io/docs/languages/js/)) and per-request logging. ## DOM events ### Propagation and delegation -- Phases: **capture** (root → target) → target → **bubble** (target → root); `{capture: true}` listens during capture. -- **Event delegation**: one listener on an ancestor plus `event.target` covers many children, including ones added later. +- [Phases](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling): capture (root → target) → target → bubble (target → root); [`{capture: true}`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture) listens during capture. +- [Event delegation](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling#event_delegation): one listener on an ancestor plus [`event.target`](https://developer.mozilla.org/en-US/docs/Web/API/Event/target) covers many children, including ones added later. ### Listener performance -- **`passive: true`** promises not to call `preventDefault()`, so scrolling starts without waiting for the handler. -- **Debounce** fires once after a pause (search-as-you-type); **throttle** fires at most once per interval (scroll, resize). +- [`passive: true`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive) promises not to call `preventDefault()`, so scrolling starts without waiting for the handler. +- [Debounce](https://developer.mozilla.org/en-US/docs/Glossary/Debounce) fires once after a pause (search-as-you-type); [throttle](https://developer.mozilla.org/en-US/docs/Glossary/Throttle) fires at most once per interval (scroll, resize). ## Recent language additions ### Temporal -- **`Temporal`** (Stage 4 in March 2026, **ES2026**) replaces `Date` with immutable values and separate types for instants, dates, times, and zoned date-times. -- Time zones are explicit (`ZonedDateTime`) and arithmetic is calendar-safe, unlike `Date`'s mutable, local-time-by-default API. +- [`Temporal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) ([Stage 4](https://github.com/tc39/proposals/blob/main/finished-proposals.md) in March 2026, ES2027) replaces `Date` with immutable values and separate types for instants, dates, times, and zoned date-times. +- Time zones are explicit ([`ZonedDateTime`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/ZonedDateTime)) and arithmetic is calendar-safe, unlike [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)'s mutable, local-time-by-default API. ### Explicit resource management -- **`using`** / `await using` (ES2026; TypeScript since 5.2) call **`[Symbol.dispose]()`** / `[Symbol.asyncDispose]()` when the block exits, even on throw — no `try/finally`. -- **`DisposableStack`** collects several resources and disposes them in reverse order. +- [`using`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using) / [`await using`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using) (ES2027; [TypeScript since 5.2](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management)) call [`[Symbol.dispose]()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/dispose) / [`[Symbol.asyncDispose]()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncDispose) when the block exits, even on throw — no `try/finally`. +- [`DisposableStack`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack) collects several resources and disposes them in reverse order. diff --git a/docs/topics/python.md b/docs/topics/python.md index f5cace2..814c338 100644 --- a/docs/topics/python.md +++ b/docs/topics/python.md @@ -6,38 +6,38 @@ What experienced Python engineers forget before an interview, grouped by subtopi ### The GIL -- CPython compiles to bytecode run by a stack-based VM; the **GIL** lets only one thread execute bytecode at a time, keeping refcounting safe without per-object locks. -- The GIL is released during blocking I/O and by some C extensions, so threads still help I/O-bound work; **CPU-bound** work needs processes or a GIL-releasing extension. -- A running thread is asked to drop the GIL every **5 ms** (`sys.getswitchinterval()`), which is why CPU-bound threads also slow I/O threads. -- The GIL doesn't make statements atomic — `x += 1` on a shared value is several bytecodes and still races; use a `Lock`. +- CPython compiles to [bytecode](https://docs.python.org/3/glossary.html#term-bytecode) run by a stack-based VM; the [GIL](https://docs.python.org/3/glossary.html#term-global-interpreter-lock) lets only one thread execute bytecode at a time, keeping refcounting safe without per-object locks. +- The GIL is released during blocking I/O and by some C extensions, so threads still help I/O-bound work; CPU-bound work needs processes or a GIL-releasing extension. +- A running thread is asked to drop the GIL every 5 ms ([`sys.getswitchinterval()`](https://docs.python.org/3/library/sys.html#sys.getswitchinterval)), which is why CPU-bound threads also slow I/O threads. +- The GIL doesn't make statements atomic — `x += 1` on a shared value is several bytecodes and still races; use a [`Lock`](https://docs.python.org/3/library/threading.html#lock-objects). ### Free threading and subinterpreters -- The **free-threaded build** (no GIL) was experimental in 3.13 and is **officially supported since 3.14** (PEP 779); an incompatible C extension can force the GIL back on. -- **`concurrent.interpreters`** (3.14, PEP 734) runs several interpreters in one process, each with its own GIL, talking over cross-interpreter queues. +- The [free-threaded build](https://docs.python.org/3/howto/free-threading-python.html) (no GIL) was experimental in 3.13 and is officially supported since 3.14 ([PEP 779](https://peps.python.org/pep-0779/)); an incompatible C extension can force the GIL back on. +- [`concurrent.interpreters`](https://docs.python.org/3/library/concurrent.interpreters.html) (3.14, [PEP 734](https://peps.python.org/pep-0734/)) runs several interpreters in one process, each with its own GIL, talking over cross-interpreter queues. ### Specializing interpreter -- **3.11** added the adaptive specializing interpreter, roughly **25%** faster on average. +- 3.11 added the [adaptive specializing interpreter](https://peps.python.org/pep-0659/), roughly [25% faster](https://docs.python.org/3/whatsnew/3.11.html#faster-cpython) on average. - Hot bytecodes are rewritten in place into type-specialized versions, which fall back when the observed types change. ## Memory management ### Refcounting and the cyclic GC -- **Reference counting** frees an object the instant its count hits zero. -- A **generational cyclic GC** periodically finds and frees reference cycles, which counting alone can't. +- [Reference counting](https://docs.python.org/3/glossary.html#term-reference-count) frees an object the instant its count hits zero (the free-threaded build can delay it). +- A generational [cyclic GC](https://docs.python.org/3/library/gc.html) periodically finds and frees reference cycles, which counting alone can't. ### Object identity and caching -- CPython caches small ints (**-5 to 256**) and interns some strings, so `is` can return `True` for equal values by accident. -- Use `==` for values and **`is` only for singletons** like `None`. +- CPython [caches small ints](https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong) (-5 to 256) and [interns](https://docs.python.org/3/library/sys.html#sys.intern) some strings, so `is` can return `True` for equal values by accident. +- Use `==` for values and [`is` only for singletons](https://peps.python.org/pep-0008/#programming-recommendations) like `None`. ### `__slots__`, weakrefs, finalizers -- **`__slots__`** replaces the per-instance `__dict__` with fixed storage — less memory per instance, no arbitrary new attributes. -- **`weakref`** holds a reference that doesn't keep the object alive — for caches and observers. -- **`__del__`** runs at refcount zero, or only at the next GC pass if the object is in a cycle (collectable since 3.4, PEP 442); use `with` for deterministic cleanup (`weakref.finalize` is a safer `__del__`, not more deterministic). +- [`__slots__`](https://docs.python.org/3/reference/datamodel.html#slots) replaces the per-instance `__dict__` with fixed storage — less memory per instance, no arbitrary new attributes. +- [`weakref`](https://docs.python.org/3/library/weakref.html) holds a reference that doesn't keep the object alive — for caches and observers. +- [`__del__`](https://docs.python.org/3/reference/datamodel.html#object.__del__) runs at refcount zero, or only at the next GC pass if the object is in a cycle (collectable since 3.4, [PEP 442](https://peps.python.org/pep-0442/)); use `with` for deterministic cleanup ([`weakref.finalize`](https://docs.python.org/3/library/weakref.html#weakref.finalize) is a safer `__del__`, not more deterministic). ## Built-in data structures @@ -47,16 +47,16 @@ What experienced Python engineers forget before an interview, grouped by subtopi |---|---|---|---| | index / lookup | O(1) | O(1) avg | O(n) | | append / add | O(1) amortized | O(1) avg | O(1) both ends | -| insert/pop at front | **O(n)** | — | **O(1)** | -| `x in c` | **O(n)** | **O(1)** avg | O(n) | +| insert/pop at front | O(n) | — | O(1) | +| `x in c` | O(n) | O(1) avg | O(n) | -- `list.sort()`/`sorted` is **Timsort**: stable, O(n log n), O(n) on nearly sorted data. -- `heapq` is a **min-heap** over a plain list; max-heap functions (`heappush_max`, `heappop_max`) exist **since 3.14**; before that, push negated keys. +- [`list.sort()`](https://docs.python.org/3/library/stdtypes.html#list.sort)/`sorted` is [Timsort](https://en.wikipedia.org/wiki/Timsort): stable, O(n log n), O(n) on nearly sorted data. +- [`heapq`](https://docs.python.org/3/library/heapq.html) is a min-heap over a plain list; max-heap functions ([`heappush_max`](https://docs.python.org/3/library/heapq.html#heapq.heappush_max), `heappop_max`) exist since 3.14; before that, push negated keys. ### dict and set internals -- A dict is an **open-addressing** hash table plus a compact, insertion-ordered entries array — that layout is why order is preserved. -- Keys must be hashable; a mutable object whose hash changes after insertion becomes unfindable. +- A dict is an [open-addressing](https://en.wikipedia.org/wiki/Open_addressing) hash table plus a [compact, insertion-ordered entries array](https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation) — that layout is why order is preserved. +- Keys must be [hashable](https://docs.python.org/3/glossary.html#term-hashable); a mutable object whose hash changes after insertion becomes unfindable. ## Concurrency @@ -64,151 +64,151 @@ What experienced Python engineers forget before an interview, grouped by subtopi | Model | Best for | Cost | |---|---|---| -| threads | I/O-bound, blocking libraries | GIL limits CPU parallelism | -| processes | CPU-bound | memory, pickling/IPC overhead | -| asyncio | many concurrent I/O waits | everything must be non-blocking | +| [threads](https://docs.python.org/3/library/threading.html) | I/O-bound, blocking libraries | GIL limits CPU parallelism | +| [processes](https://docs.python.org/3/library/multiprocessing.html) | CPU-bound | memory, pickling/IPC overhead | +| [asyncio](https://docs.python.org/3/library/asyncio.html) | many concurrent I/O waits | everything must be non-blocking | ### multiprocessing start methods -- **`fork`** copies the parent — fast, but can inherit inconsistent state (a lock held by another thread mid-acquire). +- [`fork`](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) copies the parent — fast, but can inherit inconsistent state (a lock held by another thread mid-acquire). - `spawn` starts a fresh interpreter — slower, safest; `forkserver` forks children from a clean single-threaded server process. -- **`forkserver` is the Linux default since 3.14** (previously `fork`); macOS defaults to `spawn` since 3.8, Windows always has. -- With `spawn`/`forkserver`, children **re-import the main module**, so guard entry code with `if __name__ == "__main__":`; the target, arguments, and results must pickle (no lambdas, local functions, or open sockets). +- `forkserver` is the [Linux default since 3.14](https://docs.python.org/3/whatsnew/3.14.html#whatsnew314-multiprocessing-start-method) (previously `fork`); macOS defaults to `spawn` since 3.8, Windows always has. +- With `spawn`/`forkserver`, children re-import the main module, so [guard entry code](https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods) with `if __name__ == "__main__":`; the target, arguments, and results must [pickle](https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled) (no lambdas, local functions, or open file objects). ### asyncio event loop -- One thread runs the loop: it polls ready I/O (epoll/kqueue via `selectors` on Unix; IOCP proactor on Windows since 3.8), then runs ready callbacks; a coroutine only yields control at an **`await`** on something not yet ready. -- `asyncio.create_task` schedules a coroutine; calling a coroutine function without awaiting or scheduling it **does nothing**. -- Keep a reference to created tasks — the loop holds only weak references, so an unreferenced task can be **garbage-collected mid-run**. +- One thread runs the [loop](https://docs.python.org/3/library/asyncio-eventloop.html): it polls ready I/O ([epoll](https://en.wikipedia.org/wiki/Epoll)/[kqueue](https://en.wikipedia.org/wiki/Kqueue) via [`selectors`](https://docs.python.org/3/library/selectors.html) on Unix; [IOCP proactor](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.ProactorEventLoop) on Windows since 3.8), then runs ready callbacks; a coroutine only yields control at an `await` on something not yet ready. +- [`asyncio.create_task`](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) schedules a coroutine; calling a coroutine function without awaiting or scheduling it does nothing. +- Keep a reference to created tasks — the loop holds only weak references, so an unreferenced task can be garbage-collected mid-run. ### asyncio tasks and cancellation -- **`TaskGroup`** (3.11) is structured concurrency: one failure cancels the siblings and waits for them; `gather` doesn't cancel the others by default. -- A blocking call in a coroutine (`time.sleep`, sync file I/O) **stalls the whole event loop** — use async libraries or `asyncio.to_thread`. -- Cancellation raises **`CancelledError`** at the next `await`; `finally` cleanup propagates it automatically, but an `except` that catches it must re-raise. +- [`TaskGroup`](https://docs.python.org/3/library/asyncio-task.html#task-groups) (3.11) is [structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency): one failure cancels the siblings and waits for them; [`gather`](https://docs.python.org/3/library/asyncio-task.html#asyncio.gather) doesn't cancel the others by default. +- A blocking call in a coroutine (`time.sleep`, sync file I/O) stalls the whole event loop — use async libraries or [`asyncio.to_thread`](https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread). +- [Cancellation](https://docs.python.org/3/library/asyncio-task.html#task-cancellation) raises [`CancelledError`](https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError) at the next `await`; `finally` cleanup propagates it automatically, but an `except` that catches it must re-raise. ## Gotchas ### Default arguments and late binding -- Defaults are evaluated **once, at definition time** — `def f(x, cache=[])` shares one list across calls; default to `None` and create the object inside. -- Dataclasses reject unhashable defaults (`list`, `dict`, `set`) — use **`field(default_factory=list)`**. -- A closure captures the *variable*, not its value — `[lambda: i for i in range(3)]` all return `2`; a default binds early: `lambda i=i: i`. +- [Defaults are evaluated once](https://docs.python.org/3/reference/compound_stmts.html#function-definitions), at definition time — `def f(x, cache=[])` shares one list across calls; default to `None` and create the object inside. +- Dataclasses reject unhashable defaults (`list`, `dict`, `set`) — use [`field(default_factory=list)`](https://docs.python.org/3/library/dataclasses.html#mutable-default-values). +- A closure [captures the variable, not its value](https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result) — `[lambda: i for i in range(3)]` all return `2`; a default binds early: `lambda i=i: i`. ### Scope and `UnboundLocalError` -- **LEGB** lookup: Local, Enclosing, Global, Built-in. -- Assigning to a name anywhere in a function makes it local for the **whole** body, so reading it before the assignment raises `UnboundLocalError`; use `nonlocal`/`global`. +- [LEGB](https://docs.python.org/3/reference/executionmodel.html#resolution-of-names) lookup: Local, Enclosing, Global, Built-in. +- Assigning to a name anywhere in a function makes it local for the whole body, so reading it before the assignment raises [`UnboundLocalError`](https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value); use [`nonlocal`](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement)/[`global`](https://docs.python.org/3/reference/simple_stmts.html#the-global-statement). ### Numeric gotchas -- `//` and `%` **floor toward −∞**: `-7 // 2 == -4` and `-7 % 2 == 1`; C, Go, Rust, and JavaScript truncate toward zero (`-7 % 2 == -1`). -- **`round()` rounds half to even**: `round(2.5) == 2`, `round(3.5) == 4`; `round(2.675, 2) == 2.67` because the float is slightly below 2.675. -- `int` never overflows (arbitrary precision); `float` is an IEEE 754 double, so use **`decimal.Decimal`** built from strings for money. +- [`//` and `%` floor toward −∞](https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations): `-7 // 2 == -4` and `-7 % 2 == 1`; C, Go, Rust, and JavaScript truncate toward zero (`-7 % 2 == -1`). +- [`round()`](https://docs.python.org/3/library/functions.html#round) rounds [half to even](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even): `round(2.5) == 2`, `round(3.5) == 4`; `round(2.675, 2) == 2.67` because the float is slightly below 2.675. +- `int` never overflows (arbitrary precision); `float` is an [IEEE 754 double](https://docs.python.org/3/tutorial/floatingpoint.html), so use [`decimal.Decimal`](https://docs.python.org/3/library/decimal.html) built from strings for money. ### Recursion depth -- The default limit is **1000** frames (`sys.getrecursionlimit()`); going deeper raises `RecursionError`. -- There's no **tail-call optimization**, so deep DFS or memoized recursion on large inputs needs an explicit stack. -- `sys.setrecursionlimit` helps for moderate depths; for depths around 10⁵ and beyond, rewrite iteratively. +- The default limit is 1000 frames ([`sys.getrecursionlimit()`](https://docs.python.org/3/library/sys.html#sys.getrecursionlimit)); going deeper raises [`RecursionError`](https://docs.python.org/3/library/exceptions.html#RecursionError). +- There's no [tail-call optimization](https://en.wikipedia.org/wiki/Tail_call), so deep DFS or memoized recursion on large inputs needs an explicit stack. +- [`sys.setrecursionlimit`](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit) helps for moderate depths; for depths around 10⁵ and beyond, rewrite iteratively. ### Copies and aliasing -- `[[0] * 3] * 3` builds three references to the **same** inner list — mutating one row mutates all. -- `copy.copy()` copies only the outer object; `copy.deepcopy()` recurses and handles cycles via a memo dict. +- `[[0] * 3] * 3` builds [three references to the same inner list](https://docs.python.org/3/faq/programming.html#how-do-i-create-a-multidimensional-list) — mutating one row mutates all. +- [`copy.copy()`](https://docs.python.org/3/library/copy.html) copies only the outer object; `copy.deepcopy()` recurses and handles cycles via a memo dict. ### Mutation during iteration - Removing items from a list inside `for` skips elements. -- Resizing a dict while iterating raises `RuntimeError: dictionary changed size during iteration` — iterate over a copy. +- Resizing a dict while iterating raises `RuntimeError: dictionary changed size during iteration` — iterate over a copy ([dict views](https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects)). ### Import mechanics -- Loaded modules are cached in **`sys.modules`**, so top-level code runs once per process. -- A **circular import** hands one module a partially initialized module missing the name it needs — import inside the function, break the cycle, or guard type-only imports with `TYPE_CHECKING`. +- Loaded modules are cached in [`sys.modules`](https://docs.python.org/3/reference/import.html#the-module-cache), so top-level code runs once per process. +- A [circular import](https://docs.python.org/3/faq/programming.html#what-are-the-best-practices-for-using-import-in-a-module) hands one module a partially initialized module missing the name it needs — import inside the function, break the cycle, or guard type-only imports with [`TYPE_CHECKING`](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING). ## Object model ### MRO and `super()` -- Multiple inheritance resolves via **C3 linearization** (the MRO, `Cls.__mro__`). -- **`super()`** follows the instance's MRO, not the direct parent, so every class in a mixin chain must call `super().__init__()`. +- Multiple inheritance resolves via [C3 linearization](https://docs.python.org/3/howto/mro.html) (the MRO, [`Cls.__mro__`](https://docs.python.org/3/reference/datamodel.html#type.__mro__)). +- [`super()`](https://docs.python.org/3/library/functions.html#super) follows the instance's MRO, not the direct parent, so every class in a mixin chain must call `super().__init__()`. ### Shared class attributes -- A mutable class attribute (`items = []` in the class body) is **shared by every instance**. +- A mutable [class attribute](https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables) (`items = []` in the class body) is shared by every instance. - `self.items.append(x)` mutates the shared list; `self.items = [...]` creates an instance attribute that shadows it. -- Create per-instance state in `__init__`; dataclasses force `field(default_factory=list)` for this reason. +- Create per-instance state in `__init__`; dataclasses force [`field(default_factory=list)`](https://docs.python.org/3/library/dataclasses.html#dataclasses.field) for this reason. ### Descriptors -- An object with `__get__`/`__set__`/`__delete__` on a class attribute is a **descriptor**; `property`, bound methods, `classmethod`, and `staticmethod` are all built on it. -- **Data descriptors** (define `__set__` or `__delete__`) beat the instance `__dict__`; non-data descriptors (only `__get__`, e.g. functions) lose to it. +- An object with `__get__`/`__set__`/`__delete__` on a class attribute is a [descriptor](https://docs.python.org/3/howto/descriptor.html); `property`, bound methods, `classmethod`, and `staticmethod` are all built on it. +- [Data descriptors](https://docs.python.org/3/howto/descriptor.html#descriptor-protocol) (define `__set__` or `__delete__`) beat the instance `__dict__`; non-data descriptors (only `__get__`, e.g. functions) lose to it. ### Class creation hooks -- **`__new__`** creates the instance, `__init__` only initializes it — override `__new__` for immutable subclasses or instance caching. -- **Metaclasses** customize class *creation*; **`__init_subclass__`** covers most "react when a subclass is defined" needs without one. +- [`__new__`](https://docs.python.org/3/reference/datamodel.html#object.__new__) creates the instance, `__init__` only initializes it — override `__new__` for immutable subclasses or instance caching. +- [Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) customize class creation; [`__init_subclass__`](https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__) covers most "react when a subclass is defined" needs without one. ### Equality and hashing -- Defining **`__eq__`** without `__hash__` sets `__hash__ = None`, making instances unhashable. -- `@dataclass(frozen=True)` blocks field reassignment (shallow — a list field stays mutable) and generates `__hash__`, which raises `TypeError` on an unhashable field; `slots=True` (**3.10**) adds `__slots__`. +- Defining [`__eq__` without `__hash__`](https://docs.python.org/3/reference/datamodel.html#object.__hash__) sets `__hash__ = None`, making instances unhashable. +- [`@dataclass(frozen=True)`](https://docs.python.org/3/library/dataclasses.html#frozen-instances) blocks field reassignment (shallow — a list field stays mutable) and generates `__hash__`, which raises `TypeError` on an unhashable field; [`slots=True`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass) (3.10) adds `__slots__`. ## Functions and iteration ### Generators -- Generators are lazy and **exhausted after one pass**; `yield from` delegates to a sub-generator. -- `gen.send(v)` resumes a generator and makes `v` the value of the paused `yield` expression. +- [Generators](https://docs.python.org/3/glossary.html#term-generator) are lazy and exhausted after one pass; [`yield from`](https://docs.python.org/3/reference/expressions.html#yield-expressions) delegates to a sub-generator. +- [`gen.send(v)`](https://docs.python.org/3/reference/expressions.html#generator.send) resumes a generator and makes `v` the value of the paused `yield` expression. ### Decorators -- Wrap with **`functools.wraps(func)`** or the wrapper loses `__name__`, `__doc__`, and signature. -- A decorator with arguments needs an extra layer: `@deco(arg)` calls `deco(arg)`, which must return the real decorator. +- Wrap with [`functools.wraps(func)`](https://docs.python.org/3/library/functools.html#functools.wraps) or the wrapper loses `__name__`, `__doc__`, and signature. +- A [decorator](https://docs.python.org/3/glossary.html#term-decorator) with arguments needs an extra layer: `@deco(arg)` calls `deco(arg)`, which must return the real decorator. ### functools.cache pitfalls -- **`@cache`** (3.9) is `lru_cache(maxsize=None)` — unbounded, so it grows forever on unbounded inputs; switch to `lru_cache(maxsize=N)` to bound it. -- On a method it keys on `self` and **keeps every instance alive**; use `cached_property` or a per-instance cache. +- [`@cache`](https://docs.python.org/3/library/functools.html#functools.cache) (3.9) is `lru_cache(maxsize=None)` — unbounded, so it grows forever on unbounded inputs; switch to [`lru_cache(maxsize=N)`](https://docs.python.org/3/library/functools.html#functools.lru_cache) to bound it. +- On a method it keys on `self` and [keeps every instance alive](https://docs.python.org/3/faq/programming.html#how-do-i-cache-method-calls); use [`cached_property`](https://docs.python.org/3/library/functools.html#functools.cached_property) or a per-instance cache. - Arguments must be hashable, and a returned mutable object is shared by every caller. ### Context managers and exceptions -- **`__exit__` returning `True` suppresses** the exception; `contextlib.contextmanager` turns a one-`yield` generator into a context manager. -- `try ... else` runs only when no exception was raised; **`except*`** (**3.11**) handles exception groups, e.g. from a `TaskGroup`. +- [`__exit__`](https://docs.python.org/3/reference/datamodel.html#object.__exit__) returning `True` suppresses the exception; [`contextlib.contextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager) turns a one-`yield` generator into a context manager. +- [`try ... else`](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement) runs only when no exception was raised; [`except*`](https://docs.python.org/3/reference/compound_stmts.html#except-star) (3.11) handles [exception groups](https://docs.python.org/3/library/exceptions.html#exception-groups), e.g. from a `TaskGroup`. ## Typing and recent features ### Gradual typing and protocols -- Type hints are **not enforced at runtime** — they're for `mypy`/`pyright`. -- **`typing.Protocol`** gives structural typing: a class matches by having the methods, unlike ABCs, which need subclassing or registration. -- **`TypedDict`** types a dict's known keys without making it a class. +- [Type hints](https://docs.python.org/3/library/typing.html) are not enforced at runtime — they're for [`mypy`](https://mypy.readthedocs.io/)/[`pyright`](https://github.com/microsoft/pyright). +- [`typing.Protocol`](https://docs.python.org/3/library/typing.html#typing.Protocol) gives structural typing: a class matches by having the methods, unlike [ABCs](https://docs.python.org/3/library/abc.html), which need subclassing or registration. +- [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) types a dict's known keys without making it a class. ### Generics and annotation evaluation -- **PEP 695** syntax (`class Box[T]: ...`, `type Alias = ...`, 3.12) replaces manual `TypeVar` declarations with scoped type parameters. -- Annotations are **evaluated lazily since 3.14** (**PEP 649**), making `from __future__ import annotations` largely unnecessary. +- [PEP 695](https://peps.python.org/pep-0695/) syntax (`class Box[T]: ...`, `type Alias = ...`, 3.12) replaces manual [`TypeVar`](https://docs.python.org/3/library/typing.html#typing.TypeVar) declarations with scoped type parameters. +- Annotations are [evaluated lazily since 3.14](https://docs.python.org/3/whatsnew/3.14.html#pep-649-pep-749-deferred-evaluation-of-annotations) ([PEP 649](https://peps.python.org/pep-0649/)), making `from __future__ import annotations` largely unnecessary. ### Structural pattern matching -- `match` (**3.10**) destructures by shape: `case {"type": "click", "x": x}`, `case Point(x=0)`. -- A bare name in a `case` **binds**, it doesn't compare — `case RED:` matches everything; use dotted names (`Color.RED`) for constants. +- [`match`](https://docs.python.org/3/reference/compound_stmts.html#the-match-statement) (3.10) destructures by shape: `case {"type": "click", "x": x}`, `case Point(x=0)`. +- A bare name in a `case` [binds, it doesn't compare](https://docs.python.org/3/reference/compound_stmts.html#capture-patterns) — `case RED:` matches everything; use [dotted names](https://docs.python.org/3/reference/compound_stmts.html#value-patterns) (`Color.RED`) for constants. ### Template strings -- **t-strings** (`t"Hello {name}"`, **3.14**, PEP 750) produce a `Template` object with static parts and interpolated values kept separate. -- A library receiving it can escape values safely (SQL, HTML), unlike an f-string that arrives already joined. +- [t-strings](https://docs.python.org/3/library/string.templatelib.html) (`t"Hello {name}"`, 3.14, [PEP 750](https://peps.python.org/pep-0750/)) produce a `Template` object with static parts and interpolated values kept separate. +- A library receiving it can escape values safely (SQL, HTML), unlike an [f-string](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) that arrives already joined. ## Tooling ### Packaging and tests -- `pyproject.toml` is the standard manifest; **`uv`** resolves, installs, and manages Python versions far faster than pip, with a lock file. -- **`pytest`** fixture **scope** (`function`, `class`, `module`, `session`) sets how often it's rebuilt — a `session` fixture holding mutable state leaks between tests. +- [`pyproject.toml`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) is the standard manifest; [`uv`](https://docs.astral.sh/uv/) resolves, installs, and manages Python versions far faster than pip, with a lock file. +- [`pytest` fixture scope](https://docs.pytest.org/en/stable/how-to/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session) (`function`, `class`, `module`, `session`) sets how often it's rebuilt — a `session` fixture holding mutable state leaks between tests. ### Profiling -- **`cProfile`** gives function-level call counts and cumulative time; **`py-spy`** samples a running process without code changes, safe in production. -- **`tracemalloc`** snapshots allocations by line to find memory growth. +- [`cProfile`](https://docs.python.org/3/library/profile.html) gives function-level call counts and cumulative time; [`py-spy`](https://github.com/benfred/py-spy) samples a running process without code changes, safe in production. +- [`tracemalloc`](https://docs.python.org/3/library/tracemalloc.html) snapshots allocations by line to find memory growth. diff --git a/docs/topics/rust.md b/docs/topics/rust.md index dbf7bcd..fdc1e4a 100644 --- a/docs/topics/rust.md +++ b/docs/topics/rust.md @@ -6,74 +6,76 @@ What experienced Rust engineers forget before an interview, grouped by subtopic. ### Move semantics and Copy -- A non-`Copy` value has one owner; assignment or passing **moves** it, invalidating the old binding. -- **`Copy`** is an implicit bitwise copy, allowed only if every field is `Copy` and there's no `Drop`; **`Clone`** is explicit and possibly expensive. +- A non-`Copy` value has one owner; assignment or passing [moves](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#variables-and-data-interacting-with-move) it, invalidating the old binding. +- [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) is an implicit bitwise copy, allowed only if every field is `Copy` and there's no `Drop`; [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) is explicit and possibly expensive. ### Borrow rules and NLL -- Many `&T` or one `&mut T` at a time — never both for overlapping uses. -- **Non-lexical lifetimes**: a borrow ends at its **last use**, not at the end of the block. -- **Reborrowing** (`&mut *r`, implicit when passing `r`) makes a shorter borrow, so you can call several `&mut self` methods in a row. +- Many `&T` or one `&mut T` at a time — never both for overlapping uses ([borrowing rules](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references)). +- [Non-lexical lifetimes](https://blog.rust-lang.org/2022/08/05/nll-by-default/): a borrow ends at its last use, not at the end of the block. +- Reborrowing (`&mut *r`, implicit when passing `r`) makes a shorter borrow, so you can call several `&mut self` methods in a row. ### Interior mutability | Type | Checked | Thread-safe | Note | |---|---|---|---| -| `Cell` | none needed | no | copy/replace in and out; no references through `&Cell` | -| `RefCell` | runtime | no | a conflicting borrow **panics** | -| `OnceCell`/`OnceLock` | write once | `OnceLock` yes | lazy initialization | -| `Mutex`/`RwLock` | runtime (lock) | yes | can be poisoned | +| [`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) | none needed | no | copy/replace in and out; no references through `&Cell` | +| [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) | runtime | no | a conflicting borrow panics | +| [`OnceCell`](https://doc.rust-lang.org/std/cell/struct.OnceCell.html)/[`OnceLock`](https://doc.rust-lang.org/std/sync/struct.OnceLock.html) | write once | `OnceLock` yes | lazy initialization | +| [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)/[`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) | runtime (lock) | yes | can be poisoned | ## Lifetimes ### Elision rules -- **(1)** Each elided input reference gets its own lifetime; **(2)** exactly one input lifetime → it's used for all outputs; **(3)** in methods, `&self`'s lifetime goes to all outputs. +- [Rule 1](https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions): each elided input reference gets its own lifetime. +- Rule 2: exactly one input lifetime → it's used for all outputs. +- Rule 3: in methods, `&self`'s lifetime goes to all outputs. ### `'static` and HRTBs -- **`T: 'static`** means "holds no non-static borrows" — **`String`, `Vec` satisfy it**, `Box<&'a str>` doesn't; it doesn't mean "lives forever". `&'static T` is the forever reference. -- **HRTB** (`for<'a> Fn(&'a T)`) says "works for every lifetime", needed when the reference is only created inside the callee. +- [`T: 'static`](https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html#trait-bound) means "holds no non-static borrows" — `String`, `Vec` satisfy it, `Box<&'a str>` doesn't; it doesn't mean "lives forever". `&'static T` is the forever reference. +- [HRTB](https://doc.rust-lang.org/nomicon/hrtb.html) (`for<'a> Fn(&'a T)`) says "works for every lifetime", needed when the reference is only created inside the callee. ### "Does not live long enough" -- Usually a **temporary** dropped at the end of the statement while still borrowed — bind it to a variable first. +- Usually a [temporary](https://doc.rust-lang.org/reference/destructors.html#temporary-scopes) dropped at the end of the statement while still borrowed — bind it to a variable first. - Or a struct outliving a reference it holds — find where the referent is actually owned, or store owned data instead. ## Traits and generics ### Static vs dynamic dispatch -- **Generics** are monomorphized: one copy per type, zero-cost calls, bigger binary. -- **`dyn Trait`** is unsized; `&dyn`/`Box` is a fat pointer (data + vtable): one copy, an indirect call, heterogeneous collections. -- **Dyn compatibility** (formerly "object safety"): roughly, no methods returning `Self` by value and no generic methods. +- Generics are [monomorphized](https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics): one copy per type, zero-cost calls, bigger binary. +- [`dyn Trait`](https://doc.rust-lang.org/reference/types/trait-object.html) is unsized; `&dyn`/`Box` is a fat pointer (data + vtable): one copy, an indirect call, heterogeneous collections. +- [Dyn compatibility](https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility) (formerly "object safety"): roughly, no methods returning `Self` by value and no generic methods. ### Coherence: orphan rule and blanket impls -- **Orphan rule**: implement a trait only if your crate owns the trait or the type — prevents conflicting impls across crates. -- **Blanket impls** (`impl MyTrait for T`) cover every type meeting a bound. +- [Orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules): implement a trait only if your crate owns the trait or the type — prevents conflicting impls across crates. +- [Blanket impls](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods) (`impl MyTrait for T`) cover every type meeting a bound. ### Associated types vs type parameters -- **Associated types** (`Iterator::Item`) fix one type per impl; **type parameters** allow many impls for one type (`From`, `From`). -- **GATs** (1.65) let an associated type be generic, e.g. over a lifetime — the basis of lending iterators. +- [Associated types](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types) (`Iterator::Item`) fix one type per impl; type parameters allow many impls for one type (`From`, `From`). +- [GATs](https://blog.rust-lang.org/2022/10/28/gats-stabilization/) (1.65) let an associated type be generic, e.g. over a lifetime — the basis of lending iterators. ### `Sized` and `impl Trait` -- Every type parameter is implicitly `Sized`; **`?Sized`** accepts `str`, `[T]`, or `dyn Trait` behind a pointer. -- `impl Trait` in argument position is an anonymous generic; in return position it hides the concrete type but stays static dispatch. +- Every type parameter is implicitly [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html); `?Sized` accepts `str`, `[T]`, or `dyn Trait` behind a pointer. +- [`impl Trait`](https://doc.rust-lang.org/reference/types/impl-trait.html) in argument position is an anonymous generic; in return position it hides the concrete type but stays static dispatch. ### Eq, Ord, and Hash -- `PartialEq`/`PartialOrd` allow incomparable values (NaN ≠ NaN); **`Eq`/`Ord`** promise a total order. -- `f64` implements none of `Eq`, `Ord`, or `Hash`, so it can't key a `HashMap` or `BTreeMap` or use `.sort()`; use `sort_by(f64::total_cmp)` or an ordered wrapper. -- `Hash` must agree with `Eq` (`a == b` ⇒ equal hashes), or map lookups silently fail. -- Derived `PartialOrd`/`Ord` compare fields in **declaration order** (enums by variant order). +- [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)/[`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) allow incomparable values (NaN ≠ NaN); [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html) promises an equivalence relation (`a == a`), [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html) a total order. +- `f64` implements none of `Eq`, `Ord`, or `Hash`, so it can't key a `HashMap` or `BTreeMap` or use `.sort()`; use [`sort_by(f64::total_cmp)`](https://doc.rust-lang.org/std/primitive.f64.html#method.total_cmp) or an ordered wrapper. +- [`Hash` must agree with `Eq`](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq) (`a == b` ⇒ equal hashes), or map lookups silently fail. +- [Derived `PartialOrd`/`Ord`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html#derivable) compare fields in declaration order; enums by discriminant (variant order unless set explicitly), then fields. ### Async functions in traits -- **Async fn in traits** (**1.75**) desugars to a method returning `impl Future`. -- Callers can't require that future to be `Send` without help (the `trait-variant` crate), and `dyn` dispatch still needs boxing or `async-trait`. +- [Async fn in traits](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits/) (1.75) desugars to a method returning `impl Future`. +- Callers can't require that future to be `Send` without help (the [`trait-variant`](https://docs.rs/trait-variant) crate), and `dyn` dispatch still needs boxing or [`async-trait`](https://docs.rs/async-trait). ## Closures, iterators, strings @@ -81,142 +83,142 @@ What experienced Rust engineers forget before an interview, grouped by subtopic. | Trait | Captured state | Callable | |---|---|---| -| `Fn` | borrowed immutably | many times, via `&self` (concurrently only if `Sync`) | -| `FnMut` | borrowed mutably | many times, via `&mut self` | -| `FnOnce` | moved out / consumed | once | +| [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html) | borrowed immutably | many times, via `&self` (concurrently only if `Sync`) | +| [`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html) | borrowed mutably | many times, via `&mut self` | +| [`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) | moved out / consumed | once | -- Every `Fn` is also `FnMut` and `FnOnce`; take the **loosest** bound your code allows (`FnOnce` if you call it once), so callers can pass more kinds of closures. -- **`move`** changes *how* variables are captured (by value), not which trait is implemented — a `move` closure that only reads is still `Fn`. -- Async closures (`async || { ... }`, 1.85) can borrow from their captures across `.await`, which `|| async { ... }` couldn't; bound them with **`AsyncFn`/`AsyncFnMut`/`AsyncFnOnce`**. +- Every `Fn` is also `FnMut` and `FnOnce`; take the loosest bound your code allows (`FnOnce` if you call it once), so callers can pass more kinds of closures. +- [`move`](https://doc.rust-lang.org/reference/expressions/closure-expr.html#closure-expressions) changes how variables are captured (by value), not which trait is implemented — a `move` closure that only reads is still `Fn`. +- [Async closures](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/#async-closures) (`async || { ... }`, 1.85) can borrow from their captures across `.await`, which `|| async { ... }` couldn't; bound them with [`AsyncFn`](https://doc.rust-lang.org/std/ops/trait.AsyncFn.html)/`AsyncFnMut`/`AsyncFnOnce`. ### Iterators -- Iterator adapters (`map`, `filter`, …) are **lazy**: nothing runs until a consumer (`collect`, `sum`, `for`). -- They compile to the same code as a hand-written loop — a **zero-cost abstraction**. +- [Iterator](https://doc.rust-lang.org/std/iter/index.html) adapters (`map`, `filter`, …) are [lazy](https://doc.rust-lang.org/std/iter/index.html#laziness): nothing runs until a consumer (`collect`, `sum`, `for`). +- They compile to the same code as a hand-written loop — a [zero-cost abstraction](https://doc.rust-lang.org/book/ch13-04-performance.html). ### String vs &str -- No indexing by integer (`s[0]` doesn't compile) because characters are variable-width; slicing at a non-char boundary **panics**. -- `len()` counts bytes; `chars()` yields Unicode scalar values, which still aren't user-perceived characters. +- [No indexing by integer](https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings) (`s[0]` doesn't compile) because characters are variable-width; slicing at a non-char boundary panics. +- [`len()`](https://doc.rust-lang.org/std/primitive.str.html#method.len) counts bytes; [`chars()`](https://doc.rust-lang.org/std/primitive.str.html#method.chars) yields [Unicode scalar values](https://www.unicode.org/glossary/#unicode_scalar_value), which still aren't user-perceived characters ([grapheme clusters](https://www.unicode.org/reports/tr29/)). ## Smart pointers and memory ### Box, Rc, Arc, Cow -- `Box`: single owner on the heap. **`Rc`**: shared, non-atomic count, one thread. **`Arc`**: atomic count, cross-thread. -- An `Rc`/`Arc` cycle leaks; break it with **`Weak`**. -- `Cow` borrows until a mutation forces an owned copy. +- [`Box`](https://doc.rust-lang.org/std/boxed/struct.Box.html): single owner on the heap. [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html): shared, non-atomic count, one thread. [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html): atomic count, cross-thread. +- An `Rc`/`Arc` cycle leaks; break it with [`rc::Weak`](https://doc.rust-lang.org/std/rc/struct.Weak.html) or [`sync::Weak`](https://doc.rust-lang.org/std/sync/struct.Weak.html). +- [`Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html) borrows until a mutation forces an owned copy. ### RAII, drop order, leaking -- **RAII**: resources (files, locks, sockets) are released in `Drop` when the owner goes out of scope — no `finally` needed. -- Locals drop in **reverse declaration order**; struct fields drop in declaration order. -- **`mem::forget`** is safe: leaking memory isn't undefined behavior in Rust. +- [RAII](https://doc.rust-lang.org/rust-by-example/scope/raii.html): resources (files, locks, sockets) are released in [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) when the owner goes out of scope — no `finally` needed. +- [Locals drop in reverse declaration order](https://doc.rust-lang.org/reference/destructors.html); struct fields drop in declaration order. +- [`mem::forget`](https://doc.rust-lang.org/std/mem/fn.forget.html) is safe: [leaking memory](https://doc.rust-lang.org/nomicon/leaking.html) isn't undefined behavior in Rust. ### Deref coercion, AsRef, Borrow -- **Deref coercion** converts `&String` → `&str`, `&Vec` → `&[T]`, `&Box` → `&T` automatically at calls and method lookups. -- **`AsRef`** is a cheap reference conversion for flexible parameters: `fn open>(p: P)`. -- **`Borrow`** also promises identical `Eq`/`Hash`/`Ord`, which is why `HashMap::get` accepts a `&str`. +- [Deref coercion](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion) converts `&String` → `&str`, `&Vec` → `&[T]`, `&Box` → `&T` automatically at calls and method lookups. +- [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) is a cheap reference conversion for flexible parameters: `fn open>(p: P)`. +- [`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) also promises identical `Eq`/`Hash`/`Ord`, which is why `HashMap::get` accepts a `&str`. ### Layout and niche optimization -- **Niche optimization**: `Option<&T>`, `Option>`, and `Option` are the same size as the inner type — the forbidden null or zero value encodes `None`. -- The default `repr(Rust)` may reorder fields to cut padding; **`repr(C)`** fixes C field order for FFI. +- [Niche optimization](https://doc.rust-lang.org/std/option/index.html#representation): `Option<&T>`, `Option>`, and `Option` are the same size as the inner type — the forbidden null or zero value encodes `None`. +- The default [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html#the-rust-representation) may reorder fields to cut padding; [`repr(C)`](https://doc.rust-lang.org/reference/type-layout.html#the-c-representation) fixes C field order for FFI. - `&str`, `&[T]`, and `&dyn Trait` are two words (pointer + length or vtable); an enum is its largest variant plus a tag, unless a niche absorbs the tag. ### Pin and Unpin -- **`Pin

`** stops the pointee from moving **only if it's `!Unpin`**; most types are `Unpin` and move freely. +- [`Pin

`](https://doc.rust-lang.org/std/pin/index.html) stops the pointee from moving only if it's `!Unpin`; most types are [`Unpin`](https://doc.rust-lang.org/std/marker/trait.Unpin.html) and move freely. - Async state machines are `!Unpin` because they can be self-referential — that's why futures are polled through `Pin<&mut Self>`. ## Error handling ### `?` and error crates -- `?` returns early with `Err`, converting via **`From`**; on `Option` it returns `None`. -- **`thiserror`** for libraries (typed enums callers can match); **`anyhow`** for applications (one dynamic error with context). +- [`?`](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-try-propagation-expression) returns early with `Err`, converting via [`From`](https://doc.rust-lang.org/std/convert/trait.From.html); on `Option` it returns `None`. +- [`thiserror`](https://docs.rs/thiserror) for libraries (typed enums callers can match); [`anyhow`](https://docs.rs/anyhow) for applications (one dynamic error with context). ### Integer overflow and casts -- Overflow **panics in debug builds and wraps in release** (unless `overflow-checks = true`), so state intent with `checked_*`, `wrapping_*`, `saturating_*`, or `overflowing_*`. -- `as` between integers truncates or reinterprets silently (`300_i32 as u8 == 44`, `-1_i32 as u32 == u32::MAX`); `try_from` fails instead. -- `as` from float to integer **saturates** (since 1.45), and NaN becomes 0. +- [Overflow](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow) panics in debug builds and wraps in release (unless [`overflow-checks = true`](https://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks)), so state intent with [`checked_*`](https://doc.rust-lang.org/std/primitive.i32.html#method.checked_add), `wrapping_*`, `saturating_*`, or `overflowing_*`. +- [`as` between integers](https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast) truncates or reinterprets silently (`300_i32 as u8 == 44`, `-1_i32 as u32 == u32::MAX`); [`try_from`](https://doc.rust-lang.org/std/convert/trait.TryFrom.html) fails instead. +- `as` from float to integer [saturates](https://blog.rust-lang.org/2020/07/16/Rust-1.45.0/#fixing-unsoundness-in-casts) (since 1.45), and NaN becomes 0. ### Panics and unwinding -- `panic!` **unwinds** by default, running destructors; `panic = "abort"` exits immediately — smaller binary, no recovery. -- **`catch_unwind`** stops an unwinding panic at a boundary (FFI, worker pool) — never for control flow, and useless under `abort`. +- `panic!` [unwinds](https://doc.rust-lang.org/nomicon/unwinding.html) by default, running destructors; [`panic = "abort"`](https://doc.rust-lang.org/cargo/reference/profiles.html#panic) exits immediately — smaller binary, no recovery. +- [`catch_unwind`](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html) stops an unwinding panic at a boundary (FFI, worker pool) — never for control flow, and useless under `abort`. ## Concurrency ### Send and Sync -- **`Send`**: ownership can move to another thread. **`Sync`**: `&T` can be shared (`T: Sync` ⇔ `&T: Send`). -- `Rc` is neither; `RefCell` is `Send` if `T: Send`, never `Sync`; **`MutexGuard` is not `Send`** — it must be released on the locking thread. +- [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html): ownership can move to another thread. [`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html): `&T` can be shared (`T: Sync` ⇔ `&T: Send`). +- `Rc` is neither; `RefCell` is `Send` if `T: Send`, never `Sync`; [`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html) is not `Send` — it must be released on the locking thread. ### Mutex poisoning and scoped threads -- A panic while holding a `Mutex` **poisons** it; later `lock()` calls return `Err` so callers can decide whether the data is still valid. -- **`thread::scope`** (**1.63**) lets threads borrow local data without `'static` or `Arc`, since they must finish before the scope ends. +- A panic while holding a `Mutex` [poisons](https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning) it; later `lock()` calls return `Err` so callers can decide whether the data is still valid. +- [`thread::scope`](https://doc.rust-lang.org/std/thread/fn.scope.html) (1.63) lets threads borrow local data without `'static` or `Arc`, since they must finish before the scope ends. ### Channels -- `std::sync::mpsc`: multi-producer single-consumer; `channel()` is unbounded, `sync_channel(n)` bounded. -- **crossbeam** adds multi-consumer channels and `select!`. +- [`std::sync::mpsc`](https://doc.rust-lang.org/std/sync/mpsc/index.html): multi-producer single-consumer; `channel()` is unbounded, `sync_channel(n)` bounded. +- [crossbeam](https://docs.rs/crossbeam-channel) adds multi-consumer channels and `select!`. ### Atomics and memory ordering -- Relaxed: atomicity only. **Release** store + an **Acquire** load that reads its value: happens-before between the two threads. **SeqCst**: one global order, strongest and slowest. -- x86 is strongly ordered, so a too-weak ordering often works there and fails only on ARM. +- [`Relaxed`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html): atomicity only. `Release` store + an `Acquire` load that reads its value: happens-before between the two threads. `SeqCst`: one global order, strongest and slowest. +- x86 is [strongly ordered](https://en.wikipedia.org/wiki/Memory_ordering#In_symmetric_multiprocessing_%28SMP%29_microprocessor_systems), so a too-weak ordering often works there and fails only on ARM. ## Async ### Futures and executors -- Futures are **lazy** — nothing runs until an executor (e.g. **Tokio**) polls them. +- [Futures](https://doc.rust-lang.org/std/future/trait.Future.html) are lazy — nothing runs until an executor (e.g. [Tokio](https://tokio.rs/)) polls them. - Holding a non-`Send` guard across `.await` makes the future non-`Send`, so it can't be spawned on a multi-threaded runtime. -- `tokio::spawn` needs a **`Send + 'static`** future: move owned data or `Arc` clones in, not references. -- Tokio's default runtime is multi-threaded with work stealing; `current_thread` plus a `LocalSet` runs non-`Send` futures; `JoinSet` spawns and awaits a group of tasks. +- [`tokio::spawn`](https://docs.rs/tokio/latest/tokio/task/fn.spawn.html) needs a `Send + 'static` future: move owned data or `Arc` clones in, not references. +- Tokio's default runtime is multi-threaded with [work stealing](https://en.wikipedia.org/wiki/Work_stealing); `current_thread` plus a [`LocalSet`](https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html) runs non-`Send` futures; [`JoinSet`](https://docs.rs/tokio/latest/tokio/task/struct.JoinSet.html) spawns and awaits a group of tasks. ### Blocking and locks in async code -- A blocking call inside async code stalls the worker thread and every task on it — move it to **`spawn_blocking`**. -- A `std::sync::Mutex` is fine in async code if it's **never held across `.await`** (and it's faster). -- Use **`tokio::sync::Mutex`** only when the guard must live across an `.await`. +- A blocking call inside async code stalls the worker thread and every task on it — move it to [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html). +- A `std::sync::Mutex` is fine in async code if it's never held across `.await` (and it's faster). +- Use [`tokio::sync::Mutex`](https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use) only when the guard must live across an `.await`. ### Cancellation is drop -- Dropping a future cancels it at its current `.await`, with no signal and no chance to run async cleanup. -- Dropping a Tokio `JoinHandle` **detaches** the spawned task instead — cancel with `abort()`. -- **Cancel safety**: in `select!`, a losing branch is dropped mid-await — `read_line` into a buffer can lose data, `recv()` on a channel can't. +- Dropping a [future](https://doc.rust-lang.org/std/future/trait.Future.html) cancels it at its current `.await`, with no signal and no chance to run async cleanup. +- Dropping a Tokio [`JoinHandle`](https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html) detaches the spawned task instead — cancel with `abort()`. +- [Cancel safety](https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety): in `select!`, a losing branch is dropped mid-await — `read_line` into a buffer can lose data, `recv()` on a channel can't. ## Unsafe and FFI ### What `unsafe` allows -- Five extra powers: dereference raw pointers, call unsafe functions, implement unsafe traits, access mutable statics, read union fields. -- It does **not** turn off the borrow checker for references. +- [Five extra powers](https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html#unsafe-superpowers): dereference raw pointers, call unsafe functions, implement unsafe traits, access mutable statics, read union fields. +- It does not turn off the borrow checker for references. ### Undefined behavior and Miri -- Common UB: two live `&mut` to the same data, invalid values (a `bool` that isn't 0/1), data races on non-atomic memory. -- **Miri** interprets MIR and catches much UB that "works" in normal builds. +- Common [UB](https://doc.rust-lang.org/reference/behavior-considered-undefined.html): two live `&mut` to the same data, invalid values (a `bool` that isn't 0/1), data races on non-atomic memory. +- [Miri](https://github.com/rust-lang/miri) interprets [MIR](https://rustc-dev-guide.rust-lang.org/mir/index.html) and catches much UB that "works" in normal builds. ## Macros and Cargo ### Macro kinds and hygiene -- **`macro_rules!`** pattern-matches token trees; **procedural macros** (derive, attribute, function-like) run code on a `TokenStream` at compile time. -- **Hygiene**: `macro_rules!` locals and labels don't collide with the caller's (mixed-site); items and proc-macro output are unhygienic. +- [`macro_rules!`](https://doc.rust-lang.org/reference/macros-by-example.html) pattern-matches token trees; [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html) (derive, attribute, function-like) run code on a `TokenStream` at compile time. +- [Hygiene](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene): `macro_rules!` locals and labels don't collide with the caller's (mixed-site); items and proc-macro output are unhygienic. ### Features and workspaces -- **Features are additive** — unioned across the dependency graph — so a feature must never remove functionality. -- A **workspace** shares one `Cargo.lock` and `target/` across crates. +- [Features are additive](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification) — unioned across the dependency graph — so a feature must never remove functionality. +- A [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) shares one `Cargo.lock` and `target/` across crates. ### Lock files and editions -- Commit **`Cargo.lock`** for binaries; since 2023, Cargo's guidance is to commit it for libraries too, as a CI baseline (dependents ignore it). -- Editions are opt-in, per-crate language changes; crates on different editions link together freely. -- **Edition 2024** enables **let chains** (`if let Some(x) = a && x > 0`, 1.88) and makes `impl Trait` in return position capture all in-scope lifetimes by default. +- Commit `Cargo.lock` for binaries; [since 2023](https://blog.rust-lang.org/2023/08/29/committing-lockfiles/), Cargo's guidance is to commit it for libraries too, as a CI baseline (dependents ignore it). +- [Editions](https://doc.rust-lang.org/edition-guide/editions/index.html) are opt-in, per-crate language changes; crates on different editions link together freely. +- [Edition 2024](https://doc.rust-lang.org/edition-guide/rust-2024/index.html) enables [let chains](https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/#let-chains) (`if let Some(x) = a && x > 0`, 1.88) and makes `impl Trait` in return position [capture all in-scope lifetimes](https://doc.rust-lang.org/edition-guide/rust-2024/rpit-lifetime-capture.html) by default. diff --git a/docs/topics/solidity.md b/docs/topics/solidity.md index 97beab3..2e00e70 100644 --- a/docs/topics/solidity.md +++ b/docs/topics/solidity.md @@ -6,58 +6,58 @@ What experienced Solidity engineers forget before an interview, grouped by subto ### Visibility and mutability -- `external` functions are callable internally only as `this.f()` — a real external call; `public` works both ways. -- **`private` isn't secret**: anyone can read any storage slot off-chain with `eth_getStorageAt`. -- External calls to `view`/`pure` functions use **`STATICCALL`**, so a state change inside them reverts at runtime. +- [`external`](https://docs.soliditylang.org/en/latest/contracts.html#function-visibility) functions are callable internally only as `this.f()` — a real external call; `public` works both ways. +- `private` isn't secret: anyone can read any storage slot off-chain with [`eth_getStorageAt`](https://ethereum.org/developers/docs/apis/json-rpc/#eth-getstorageat). +- External calls to [`view`/`pure`](https://docs.soliditylang.org/en/latest/contracts.html#view-functions) functions use `STATICCALL` ([EIP-214](https://eips.ethereum.org/EIPS/eip-214)), so a state change inside them reverts at runtime — except library `view` functions, called with `DELEGATECALL` and not enforced. ### Inheritance and linearization -- Bases are listed **most base-like first**: `contract D is B, C` (both `is A`) linearizes to D → C → B → A (C3, like Python's MRO). -- **`super`** calls the next contract in the linearization, not the declared parent: in D, `super.f()` inside C calls B even though C only inherits A. -- Overridable functions need `virtual`; overriding needs `override`, and `override(B, C)` when several bases define it; implementing a single interface's function needs no `override` since 0.8.8. +- Bases are listed most base-like first: `contract D is B, C` (both `is A`) [linearizes](https://docs.soliditylang.org/en/latest/contracts.html#multiple-inheritance-and-linearization) to D → C → B → A ([C3](https://docs.python.org/3/howto/mro.html), like Python's MRO). +- `super` calls the next contract in the linearization, not the declared parent: in D, `super.f()` inside C calls B even though C only inherits A. +- Overridable functions need `virtual`; [overriding](https://docs.soliditylang.org/en/latest/contracts.html#function-overriding) needs `override`, and `override(B, C)` when several bases define it; implementing a single interface's function needs no `override` since 0.8.8. ### Modifiers -- `_` marks where the function body runs; a `return` in the body only leaves the body, so code after `_` still runs **after the return**. +- `_` marks where the function body runs in a [modifier](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers); a `return` in the body only leaves the body, so code after `_` still runs after the return. - Modifiers apply left to right; `_` may run several times, and if it never runs the body is skipped and return values stay at their defaults. -- Legacy codegen **inlines** modifier code at every use — move the logic into an internal function to shrink bytecode; via-IR emits modifiers as functions. +- Legacy codegen inlines modifier code at every use — move the logic into an internal function to shrink bytecode; [via-IR](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html) emits modifiers as functions. ### Libraries and user-defined types -- `internal` library functions are inlined into the caller (a `JUMP`); `public`/`external` ones run through **`DELEGATECALL`** into a separately deployed, linked library. -- Libraries have no state variables and can't receive ETH; `using L for T` attaches their functions to a type, and `global` (user-defined types only) applies it in every file. -- **User-defined value types** (`type Price is uint256;`, 0.8.8) are zero-cost wrappers that stop mixing units; `using {add as +} for Price global` binds operators. +- `internal` [library](https://docs.soliditylang.org/en/latest/contracts.html#libraries) functions are inlined into the caller (a `JUMP`); `public`/`external` ones run through `DELEGATECALL` into a separately deployed, linked library. +- Libraries have no state variables and can't receive ETH; [`using L for T`](https://docs.soliditylang.org/en/latest/contracts.html#using-for) attaches their functions to a type, and `global` (user-defined types only) applies it in every file. +- [User-defined value types](https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types) (`type Price is uint256;`, 0.8.8) are zero-cost wrappers that stop mixing units; `using {add as +} for Price global` binds operators. ### Arithmetic and casts -- Overflow and underflow **revert since 0.8** with `Panic(0x11)`; `unchecked { }` opts out for proven-safe math. -- **Explicit downcasts truncate silently**, even in 0.8: `uint8(uint256(300)) == 44` — use SafeCast. +- Overflow and underflow [revert since 0.8](https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic) with `Panic(0x11)`; `unchecked { }` opts out for proven-safe math. +- [Explicit downcasts](https://docs.soliditylang.org/en/latest/types.html#explicit-conversions) truncate silently, even in 0.8: `uint8(uint256(300)) == 44` — use [`SafeCast`](https://docs.openzeppelin.com/contracts/5.x/api/utils#SafeCast). - `int` ↔ `uint` conversions reinterpret bits: `uint256(int256(-1)) == type(uint256).max`. -- Division **truncates**, so multiply before dividing; OpenZeppelin's `Math.mulDiv` keeps a 512-bit intermediate so the product can't overflow. +- Division truncates, so multiply before dividing; OpenZeppelin's [`Math.mulDiv`](https://docs.openzeppelin.com/contracts/5.x/api/utils#Math-mulDiv-uint256-uint256-uint256-) keeps a 512-bit intermediate so the product can't overflow. ## Data and storage ### Data locations -- **`storage`** is persistent state; `memory` is per call; **`calldata`** is read-only input — the cheapest location for external array and struct parameters. -- Storage → memory assignment **copies**; memory → memory copies only the reference; a local `storage` variable is a pointer into state. +- `storage` is persistent state; `memory` is per call; `calldata` is read-only input — the cheapest [data location](https://docs.soliditylang.org/en/latest/types.html#data-location) for external array and struct parameters. +- Storage → memory assignment [copies](https://docs.soliditylang.org/en/latest/types.html#data-location-and-assignment-behavior); memory → memory copies only the reference; a local `storage` variable is a pointer into state. ### Storage layout -- Variables under 32 bytes **pack** into shared slots in declaration order; structs and arrays always start a new slot. -- A `mapping` value lives at **`keccak256(key . slot)`**; a dynamic array stores its length at the slot and elements from `keccak256(slot)`. -- `string`/`bytes` up to 31 bytes sit in the slot itself with `length * 2` in the lowest byte; longer ones store `length * 2 + 1` there and data from `keccak256(slot)`. -- With inheritance, slots follow the **C3 linearization**, most base-like contract first — reordering bases shifts every slot. +- Variables under 32 bytes [pack](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html) into shared slots in declaration order; structs and arrays always start a new slot. +- A `mapping` value lives at [`keccak256(key . slot)`](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays); a dynamic array stores its length at the slot and elements from `keccak256(slot)`. +- [`string`/`bytes`](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#bytes-and-string) up to 31 bytes sit in the slot itself with `length * 2` in the lowest byte; longer ones store `length * 2 + 1` there and data from `keccak256(slot)`. +- With inheritance, slots follow the [C3 linearization](https://docs.python.org/3/howto/mro.html), most base-like contract first — reordering bases shifts every slot. ### Transient and relocated storage -- **`transient`** state variables (0.8.28, value types only) compile to `TSTORE`/`TLOAD`: 100 gas, cleared at the end of the transaction — see [ethereum.md](ethereum.md). -- They can't have initializers, and they **persist across calls within one transaction** — a lock or flag must be reset explicitly, or a later call in a batch sees it. +- [`transient`](https://docs.soliditylang.org/en/latest/contracts.html#transient-storage) state variables (0.8.28, value types only) compile to `TSTORE`/`TLOAD` ([EIP-1153](https://eips.ethereum.org/EIPS/eip-1153)): 100 gas, cleared at the end of the transaction — see [ethereum.md](ethereum.md). +- They can't have initializers, and they persist across calls within one transaction — a lock or flag must be reset explicitly, or a later call in a batch sees it. ### Mapping and delete gotchas -- Mappings have no length and can't be iterated; every key "exists" with a zero value — keep an `EnumerableSet`/`EnumerableMap` to enumerate. -- **`delete` on a struct skips its mappings**: the entries survive and reappear if the struct is reused. +- [Mappings](https://docs.soliditylang.org/en/latest/types.html#mapping-types) have no length and can't be iterated; every key "exists" with a zero value — keep an [`EnumerableSet`](https://docs.openzeppelin.com/contracts/5.x/api/utils#EnumerableSet)/`EnumerableMap` to enumerate. +- [`delete` on a struct skips its mappings](https://docs.soliditylang.org/en/latest/types.html#delete): the entries survive and reappear if the struct is reused. - `delete arr[i]` zeroes the element without shrinking the array; swap with the last element and `pop()` to remove it. - `delete` on a dynamic storage array clears every element — its gas grows with the length. @@ -65,121 +65,121 @@ What experienced Solidity engineers forget before an interview, grouped by subto ### Function selectors and fallback -- Calldata starts with a 4-byte **selector** (first 4 bytes of `keccak256("fn(types)")`) followed by ABI-encoded arguments; selectors can collide, a risk in proxy dispatchers. -- No matching selector → **`fallback()`**; plain ETH with empty calldata → **`receive()`** (or a payable fallback); a contract with neither rejects plain ETH. +- Calldata starts with a 4-byte [selector](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector) (first 4 bytes of `keccak256("fn(types)")`) followed by ABI-encoded arguments; selectors can collide, a risk in proxy dispatchers. +- No matching selector → [`fallback()`](https://docs.soliditylang.org/en/latest/contracts.html#fallback-function); plain ETH with empty calldata → [`receive()`](https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function) (or a payable fallback); a contract with neither rejects plain ETH. ### call, delegatecall, staticcall -- `call` runs in the callee's context; **`delegatecall`** runs the callee's code on the **caller's** storage, `msg.sender`, and `msg.value`; `staticcall` forbids state changes. -- The **63/64 rule** (EIP-150): a call forwards at most 63/64 of remaining gas, keeping 1/64 for the caller. -- `delegatecall` keeps `msg.value`, so a payable multicall that delegatecalls itself credits one payment many times (the 2021 SushiSwap MISO bug); the same happens reading `msg.value` in a loop. +- [`call`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#members-of-address-types) runs in the callee's context; `delegatecall` runs the callee's code on the caller's storage, `msg.sender`, and `msg.value`; `staticcall` forbids state changes. +- The 63/64 rule ([EIP-150](https://eips.ethereum.org/EIPS/eip-150)): a call forwards at most 63/64 of remaining gas, keeping 1/64 for the caller. +- `delegatecall` keeps `msg.value`, so a payable multicall that delegatecalls itself credits one payment many times (the [2021 SushiSwap MISO bug](https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong)); the same happens reading `msg.value` in a loop. ### Sending ETH -- `transfer` reverts and `send` returns `false` on failure; both forward only a **2,300-gas stipend** — too little for most smart wallets, and brittle when opcode costs change (EIP-1884 broke receivers in 2019). -- Both are **deprecated since 0.8.31**, ahead of removal in 0.9; use `(bool ok, ) = to.call{value: amount}("")`, check `ok`, and guard against reentrancy. -- ETH can arrive without running your code — another contract's `selfdestruct`, fee recipient or withdrawal credits — so **`address(this).balance`** can exceed internal accounting; never use it in a strict equality. +- [`transfer` reverts and `send` returns `false`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#members-of-address-types) on failure; both forward only a 2,300-gas stipend — too little for most smart wallets, and brittle when opcode costs change ([EIP-1884](https://eips.ethereum.org/EIPS/eip-1884) broke receivers in 2019). +- Both are [deprecated since 0.8.31](https://github.com/argotorg/solidity/releases/tag/v0.8.31), ahead of removal in 0.9; use `(bool ok, ) = to.call{value: amount}("")`, check `ok`, and guard against reentrancy. +- ETH can arrive without running your code — another contract's `selfdestruct`, fee recipient or withdrawal credits — so `address(this).balance` can exceed internal accounting; never use it in a strict equality. ### ABI encoding pitfalls -- A low-level call to an address with **no code succeeds** — check `code.length` first when you expect a contract (precompiles have no code either); high-level calls revert instead. -- **`abi.encodePacked`** of several dynamic values can collide (`("a","bc")` vs `("ab","c")`) — hash `abi.encode` output for signatures. -- **`abi.encodeCall`** (0.8.11) type-checks the function and arguments; `abi.encodeWithSignature` silently accepts a typo in the signature string. +- A low-level call to an address with no code succeeds — check `code.length` first when you expect a contract ([precompiles](https://www.evm.codes/precompiled) have no code either); [high-level calls revert instead](https://docs.soliditylang.org/en/latest/control-structures.html#external-function-calls). +- [`abi.encodePacked`](https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode) of several dynamic values can collide (`("a","bc")` vs `("ab","c")`) — hash `abi.encode` output for signatures. +- [`abi.encodeCall`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#abi-encoding-and-decoding-functions) (0.8.11) type-checks the function and arguments; `abi.encodeWithSignature` silently accepts a typo in the signature string. ### Event encoding -- An **indexed** `string`, `bytes`, array, or struct is stored as its **keccak256 hash** — the value can't be recovered from the log. -- **`anonymous`** events drop the signature topic, allowing 4 indexed parameters, but can't be filtered by event name. +- An [indexed](https://docs.soliditylang.org/en/latest/abi-spec.html#events) `string`, `bytes`, array, or struct is stored as its keccak256 hash — the value can't be recovered from the log. +- [`anonymous` events](https://docs.soliditylang.org/en/latest/contracts.html#events) drop the signature topic, allowing 4 indexed parameters, but can't be filtered by event name. - Non-indexed parameters are ABI-encoded into the log data; contracts can't read logs back — see [ethereum.md](ethereum.md). ## Security ### Reentrancy -- An external call re-enters before state updates: **single-function**, cross-function, cross-contract, or **read-only** (a view function returns pre-update state to another protocol). -- Fix with **Checks-Effects-Interactions** plus a reentrancy guard; OpenZeppelin's `ReentrancyGuardTransient` keeps the lock in transient storage. -- Hooks are external calls too: ERC-721/1155 `safeTransfer` receivers, ERC-777 hooks, and ETH sent to a contract. +- An external call [re-enters](https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy) before state updates: single-function, cross-function, cross-contract, or read-only (a view function returns pre-update state to another protocol). +- Fix with [Checks-Effects-Interactions](https://docs.soliditylang.org/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern) plus a reentrancy guard; OpenZeppelin's [`ReentrancyGuardTransient`](https://docs.openzeppelin.com/contracts/5.x/api/utils#ReentrancyGuardTransient) keeps the lock in transient storage. +- Hooks are external calls too: [ERC-721](https://eips.ethereum.org/EIPS/eip-721)/[1155](https://eips.ethereum.org/EIPS/eip-1155) `safeTransfer` receivers, [ERC-777](https://eips.ethereum.org/EIPS/eip-777) hooks, and ETH sent to a contract. ### Access control -- Missing modifiers and **unprotected initializers** let anyone call admin functions. -- Authenticate with `msg.sender`, never **`tx.origin`** — a malicious contract the owner calls can pass a `tx.origin` check. -- **`msg.sender == tx.origin`** no longer proves there is no code in the path: since EIP-7702 (Pectra, May 2025) a delegated EOA runs code and makes several calls per transaction. +- Missing modifiers and unprotected [initializers](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable) let anyone call admin functions. +- Authenticate with `msg.sender`, never [`tx.origin`](https://docs.soliditylang.org/en/latest/security-considerations.html#tx-origin) — a malicious contract the owner calls can pass a `tx.origin` check. +- `msg.sender == tx.origin` no longer proves there is no code in the path: since [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) (Pectra, May 2025) a delegated EOA runs code and makes several calls per transaction. - `code.length == 0` doesn't prove an EOA either: it's 0 for a contract still in its constructor, and a 7702-delegated EOA has code. ### Admin keys and governance -- **`Ownable2Step`** makes the new owner accept the transfer, so a typo in the address can't brick ownership. -- Route admin actions through a **timelock** controlled by a multisig, giving users a window to exit before a change lands. -- Flash-loan governance attacks borrow voting power for one transaction (Beanstalk, April 2022, ~$182M lost); snapshot voting power at proposal creation (`ERC20Votes` checkpoints) and add a voting delay. +- [`Ownable2Step`](https://docs.openzeppelin.com/contracts/5.x/api/access#Ownable2Step) makes the new owner accept the transfer, so a typo in the address can't brick ownership. +- Route admin actions through a [timelock](https://docs.openzeppelin.com/contracts/5.x/api/governance#TimelockController) controlled by a multisig, giving users a window to exit before a change lands. +- Flash-loan governance attacks borrow voting power for one transaction ([Beanstalk](https://rekt.news/beanstalk-rekt/), April 2022, ~$182M lost); snapshot voting power at proposal creation ([`ERC20Votes`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#ERC20Votes) checkpoints) and add a voting delay. ### Signature replay and malleability -- Signed messages without a **nonce, chainId, and deadline** can be replayed on another chain or later. -- ECDSA `s` can be flipped (`n − s`) into a second valid signature — enforce **low-`s`** and never use a signature as a unique ID. -- Raw `ecrecover` returns `address(0)` on a bad signature, which matches an unset signer — use OpenZeppelin's `ECDSA`, which reverts and enforces low-`s`. -- Smart-contract wallets (Safe, ERC-4337 accounts) sign via **ERC-1271** `isValidSignature`; OpenZeppelin's `SignatureChecker` tries ECDSA, then ERC-1271, and ERC-6492 wraps signatures from wallets not yet deployed. +- Signed messages without a nonce, chainId, and deadline can be replayed on another chain or later ([EIP-712](https://eips.ethereum.org/EIPS/eip-712) domains bind the chain and contract). +- ECDSA `s` can be flipped (`n − s`) into a second valid signature — enforce low-`s` ([EIP-2](https://eips.ethereum.org/EIPS/eip-2)) and never use a signature as a unique ID. +- Raw [`ecrecover`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions) returns `address(0)` on a bad signature, which matches an unset signer — use OpenZeppelin's [`ECDSA`](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#ECDSA), which reverts and enforces low-`s`. +- Smart-contract wallets (Safe, [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) accounts) sign via [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) `isValidSignature`; OpenZeppelin's [`SignatureChecker`](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#SignatureChecker) tries ECDSA, then ERC-1271, and [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) wraps signatures from wallets not yet deployed. ### Denial of service -- **Unbounded loops** over user-growable arrays eventually exceed the gas limit — paginate or cap the size. -- Pushing ETH to many receivers fails if one reverts (a contract without `receive`) — switch to **pull payments** (a withdraw function). -- A malicious callee can return huge data that Solidity copies into memory even when discarded — a **returnbomb**; cap the copy with an assembly `call`. +- Unbounded loops over user-growable arrays eventually exceed the gas limit — paginate or cap the size. +- Pushing ETH to many receivers fails if one reverts (a contract without `receive`) — switch to [pull payments](https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts) (a withdraw function). +- A malicious callee can return huge data that Solidity copies into memory even when discarded — a returnbomb; cap the copy with an assembly `call` (as in [`ExcessivelySafeCall`](https://github.com/nomad-xyz/ExcessivelySafeCall)). ### Randomness and block data -- **`block.prevrandao`** (0.8.18, replaced `difficulty`) is known to the proposer, who can bias it by withholding a block — use **Chainlink VRF** or commit-reveal. -- `blockhash(n)` returns 0 for blocks older than **256**; EIP-2935 (Pectra) serves 8,191 hashes from a system contract, but the opcode is unchanged. +- [`block.prevrandao`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#block-and-transaction-properties) (0.8.18, replaced `difficulty`; [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399)) is known to the proposer, who can bias it by withholding a block — use [Chainlink VRF](https://docs.chain.link/vrf) or commit-reveal. +- `blockhash(n)` returns 0 for blocks older than 256; [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) (Pectra) serves 8,191 hashes from a system contract, but the opcode is unchanged. - Anything derived from block data can be computed by an attacker's contract in the same transaction, which reverts when the outcome is bad. ## Integration risks ### Token integration quirks -- Non-standard ERC-20s (USDT) return nothing or `false` from `transfer` — use **SafeERC20**. -- **Fee-on-transfer** and rebasing tokens break "amount sent = amount received" assumptions — measure balance deltas. -- Changing a non-zero allowance lets the spender front-run the change and spend old + new. -- USDT's `approve` reverts unless the current allowance is 0 — `SafeERC20.forceApprove` resets it first. +- [Non-standard ERC-20s](https://github.com/d-xo/weird-erc20) (USDT) return nothing or `false` from `transfer` — use [`SafeERC20`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#SafeERC20). +- Fee-on-transfer and rebasing tokens break "amount sent = amount received" assumptions — measure balance deltas. +- Changing a non-zero allowance lets the spender [front-run the change](https://eips.ethereum.org/EIPS/eip-20#approve) and spend old + new. +- USDT's `approve` reverts unless the current allowance is 0 — [`SafeERC20.forceApprove`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#SafeERC20-forceApprove-contract-IERC20-address-uint256-) resets it first. - Blocklists (USDC, USDT) make any transfer to or from a flagged address revert. ### Oracle integration - Never price assets off a DEX spot price — manipulation and TWAPs in [ethereum.md](ethereum.md). -- Chainlink `latestRoundData`: check **staleness** (`updatedAt` against the feed's heartbeat) and `answer > 0`; on L2s also check the **sequencer uptime feed**. -- Feeds have their own decimals (USD pairs use 8, ETH pairs 18) — scale before combining with token amounts. +- Chainlink [`latestRoundData`](https://docs.chain.link/data-feeds/api-reference#latestrounddata): check staleness (`updatedAt` against the feed's heartbeat) and `answer > 0`; on L2s also check the [sequencer uptime feed](https://docs.chain.link/data-feeds/l2-sequencer-feeds). +- Feeds have their own [decimals](https://docs.chain.link/data-feeds/api-reference#decimals) (USD pairs use 8, ETH pairs 18) — scale before combining with token amounts. ### Front-running and slippage -- Public-mempool transactions can be front-run or **sandwiched**: the attacker trades right before and after the victim's swap. -- Swap and liquidity functions take a **`minAmountOut`** and a `deadline` chosen off-chain by the user; a minimum computed on-chain from the current spot price protects nothing. -- Use commit-reveal for bids, auctions, and games where seeing a pending transaction gives an edge; MEV mechanics in [ethereum.md](ethereum.md). +- Public-mempool transactions can be front-run or [sandwiched](https://ethereum.org/developers/docs/mev/#mev-examples-sandwich-trading): the attacker trades right before and after the victim's swap. +- Swap and liquidity functions take a `minAmountOut` and a `deadline` chosen off-chain by the user; a minimum computed on-chain from the current spot price protects nothing. +- Use [commit-reveal](https://en.wikipedia.org/wiki/Commitment_scheme) for bids, auctions, and games where seeing a pending transaction gives an edge; MEV mechanics in [ethereum.md](ethereum.md). ### Rounding and precision -- Round **in the protocol's favor**: ERC-4626 rounds shares down on deposit and up on withdraw. -- **First-depositor inflation attack**: the attacker mints 1 wei of shares and donates assets, so the next depositor's shares round down to 0 — mitigate with virtual shares (OpenZeppelin's decimals offset) or dead shares. +- Round in the protocol's favor: [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626#security-considerations) rounds shares down on deposit and up on withdraw. +- [First-depositor inflation attack](https://docs.openzeppelin.com/contracts/5.x/erc4626#security-concern-inflation-attack): the attacker mints 1 wei of shares and donates assets, so the next depositor's shares round down to 0 — mitigate with virtual shares ([OpenZeppelin's decimals offset](https://docs.openzeppelin.com/contracts/5.x/erc4626#defending-with-a-virtual-offset)) or dead shares. - Tokens mix decimals (USDC 6, WETH 18) — normalize before math and never compare raw amounts across tokens. ## Gas optimization ### Gas optimization patterns -- `SSTORE` cost compares the slot's value at transaction start, now, and new: a clean slot (now == start) costs **20,000** gas from zero or 2,900 from non-zero; a dirty slot or a no-op costs 100; +2,100 if cold — see [ethereum.md](ethereum.md). -- **Pack** small variables that are read and written together into one slot. -- **`constant`/`immutable`** values are embedded in bytecode — no `SLOAD`. +- `SSTORE` cost compares the slot's value at transaction start, now, and new ([EIP-2200](https://eips.ethereum.org/EIPS/eip-2200)): a clean slot (now == start) costs 20,000 gas from zero or 2,900 from non-zero; a dirty slot or a no-op costs 100; +2,100 if cold ([EIP-2929](https://eips.ethereum.org/EIPS/eip-2929)) — see [ethereum.md](ethereum.md). +- [Pack](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html) small variables that are read and written together into one slot. +- [`constant`/`immutable`](https://docs.soliditylang.org/en/latest/contracts.html#constant-and-immutable-state-variables) values are embedded in bytecode — no `SLOAD`. - Cache storage reads in local variables inside loops; use `calldata` parameters. -- Custom errors are cheaper than revert strings; the compiler already skips overflow checks on simple loop-counter increments, so `unchecked { ++i; }` is no longer needed there. +- [Custom errors](https://docs.soliditylang.org/en/latest/contracts.html#errors) are cheaper than revert strings; the compiler [already skips overflow checks](https://soliditylang.org/blog/2023/10/25/solidity-0.8.22-release-announcement/) on simple loop-counter increments, so `unchecked { ++i; }` is no longer needed there. ### Code size limit -- Deployed code is capped at **24,576 bytes** (EIP-170) and initcode at 49,152 (EIP-3860, Shanghai). -- Workarounds: move logic into external libraries, split into several contracts, lower optimizer `runs`, or use the Diamond pattern. +- Deployed code is capped at 24,576 bytes ([EIP-170](https://eips.ethereum.org/EIPS/eip-170)) and initcode at 49,152 ([EIP-3860](https://eips.ethereum.org/EIPS/eip-3860), Shanghai). +- Workarounds: move logic into external libraries, split into several contracts, lower optimizer `runs`, or use the [Diamond](https://eips.ethereum.org/EIPS/eip-2535) pattern. ### Clones and factories -- An **EIP-1167 minimal proxy** is 45 bytes of runtime code that `delegatecall`s a fixed implementation, so deploying many instances (vaults, wallets, pools) costs a fraction of a full deployment. +- An [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy is 45 bytes of runtime code that `delegatecall`s a fixed implementation, so deploying many instances (vaults, wallets, pools) costs a fraction of a full deployment. - Clones can't be upgraded and run no constructor: they need an initializer, and each call pays an extra `DELEGATECALL`. -- **`CREATE2`** sets the address to `keccak256(0xff ++ deployer ++ salt ++ keccak256(initcode))[12:]`, known before deployment (counterfactual wallets). +- [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) sets the address to `keccak256(0xff ++ deployer ++ salt ++ keccak256(initcode))[12:]`, known before deployment (counterfactual wallets). - Derive the salt from the caller and the init parameters, or a front-runner can take the predicted address with other arguments. ## Upgradeability @@ -188,85 +188,85 @@ What experienced Solidity engineers forget before an interview, grouped by subto | Pattern | Upgrade logic lives in | Note | |---|---|---| -| **Transparent** | proxy (admin-only path) | admin can't call the implementation | -| **UUPS** (ERC-1822) | implementation | cheaper proxy; an implementation without upgrade code bricks it | -| **Beacon** | shared beacon contract | upgrades many proxies at once | -| **Diamond** (EIP-2535) | proxy routing to many facets | per-selector implementations | +| [Transparent](https://docs.openzeppelin.com/contracts/5.x/api/proxy#TransparentUpgradeableProxy) | proxy (admin-only path) | admin can't call the implementation | +| [UUPS](https://docs.openzeppelin.com/contracts/5.x/api/proxy#UUPSUpgradeable) ([ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) | implementation | cheaper proxy; an implementation without upgrade code bricks it | +| [Beacon](https://docs.openzeppelin.com/contracts/5.x/api/proxy#BeaconProxy) | shared beacon contract | upgrades many proxies at once | +| Diamond ([EIP-2535](https://eips.ethereum.org/EIPS/eip-2535)) | proxy routing to many facets | per-selector implementations | -- Every pattern keeps **storage in the proxy** and `delegatecall`s the implementation. +- Every pattern keeps storage in the proxy and `delegatecall`s the implementation. ### Proxy storage and initializers -- The implementation address sits at the pseudo-random **EIP-1967** slot, away from the implementation's own variables. -- New versions only append storage variables, never reorder — or use **ERC-7201** namespaced storage. -- Constructors don't run through a proxy: use **initializers**, and call `_disableInitializers()` in the implementation's constructor. +- The implementation address sits at the pseudo-random [EIP-1967](https://eips.ethereum.org/EIPS/eip-1967) slot, away from the implementation's own variables. +- New versions only append storage variables, never reorder — or use [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) namespaced storage. +- Constructors don't run through a proxy: use [initializers](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable), and call [`_disableInitializers()`](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable-_disableInitializers--) in the implementation's constructor. - `immutable` values live in the implementation's bytecode, so every proxy shares the values set at implementation deployment. ### Upgrade safety - A proxy function whose selector matches an implementation function shadows it — Transparent proxies route by caller, UUPS proxies have no functions of their own. -- An **uninitialized UUPS implementation** let anyone take ownership and upgrade it to a `selfdestruct` contract, bricking every proxy (OpenZeppelin advisory, 2021); since EIP-6780 the code survives. -- **Metamorphic contracts** redeployed different code at one address via `CREATE2` + `SELFDESTRUCT`; EIP-6780 (Cancun) ended the trick. -- OpenZeppelin's Upgrades plugins (Foundry, Hardhat) diff storage layouts and flag unsafe patterns before an upgrade. +- An uninitialized UUPS implementation let anyone take ownership and upgrade it to a `selfdestruct` contract, bricking every proxy ([OpenZeppelin advisory, 2021](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-5vp3-v4hc-gx76)); since [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) the code survives. +- [Metamorphic contracts](https://github.com/0age/metamorphic) redeployed different code at one address via `CREATE2` + `SELFDESTRUCT`; EIP-6780 (Cancun) ended the trick. +- OpenZeppelin's [Upgrades plugins](https://docs.openzeppelin.com/upgrades-plugins/) (Foundry, Hardhat) diff storage layouts and flag unsafe patterns before an upgrade. ## Errors and reverts ### Error types -- `require(cond, "msg")` and `revert("msg")` encode **`Error(string)`** (selector `0x08c379a0`). -- `assert`, overflow, division by zero, and out-of-bounds access raise **`Panic(uint256)`** with codes `0x01`, `0x11`, `0x12`, `0x32`. -- Before 0.8, `assert` hit the `INVALID` opcode and burned all remaining gas; now it reverts like any other error. -- **Custom errors** (0.8.4) encode a selector plus arguments; `require(cond, MyError())` accepts them too. +- `require(cond, "msg")` and `revert("msg")` encode [`Error(string)`](https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require) (selector `0x08c379a0`). +- `assert`, overflow, division by zero, and out-of-bounds access raise `Panic(uint256)` with codes `0x01`, `0x11`, `0x12`, `0x32`. +- [Before 0.8](https://docs.soliditylang.org/en/latest/080-breaking-changes.html), `assert` hit the `INVALID` opcode and burned all remaining gas; now it reverts like any other error. +- [Custom errors](https://docs.soliditylang.org/en/latest/contracts.html#errors) (0.8.4) encode a selector plus arguments; [`require(cond, MyError())`](https://soliditylang.org/blog/2024/09/04/solidity-0.8.27-release-announcement/) accepts them since 0.8.26 (via-IR) / 0.8.27 (legacy). ### try/catch limits -- Works only on **external calls** and `new` contract creation, not on internal calls. -- Catches only reverts inside the callee: undecodable return data or **a target with no code** reverts the caller. +- [`try`/`catch`](https://docs.soliditylang.org/en/latest/control-structures.html#try-catch) works only on external calls and `new` contract creation, not on internal calls. +- Catches only reverts inside the callee: undecodable return data or a target with no code reverts the caller. - Clauses: `catch Error(string memory)`, `catch Panic(uint256)`, and `catch (bytes memory)` for everything else — a custom error can't be caught by name. -- The callee can burn 63/64 of the gas, leaving too little for the `catch` block. +- The callee can burn 63/64 of the gas ([EIP-150](https://eips.ethereum.org/EIPS/eip-150)), leaving too little for the `catch` block. ### Revert data bubbling -- A low-level `call` returns `(bool success, bytes memory data)`: a callee revert gives **`success == false`** instead of bubbling up — always check it. -- Re-throw the callee's error unchanged with assembly: `revert(add(data, 32), mload(data))`. -- OpenZeppelin's `Address.functionCall` does both. +- A low-level [`call`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#members-of-address-types) returns `(bool success, bytes memory data)`: a callee revert gives `success == false` instead of bubbling up — always check it. +- Re-throw the callee's error unchanged with [assembly](https://docs.soliditylang.org/en/latest/assembly.html): `revert(add(data, 32), mload(data))`. +- OpenZeppelin's [`Address.functionCall`](https://docs.openzeppelin.com/contracts/5.x/api/utils#Address-functionCall-address-bytes-) does both. ## Compiler and tooling ### Compiler pipelines -- **via-IR** compiles through Yul: stronger cross-function optimization and fewer "stack too deep" errors (it moves variables to memory), but slower builds; legacy is still the default. +- [via-IR](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html) compiles through [Yul](https://docs.soliditylang.org/en/latest/yul.html): stronger cross-function optimization and fewer "stack too deep" errors (it moves variables to memory), but slower builds; legacy is still the default. - via-IR changes some semantics: each contract initializes its state variables right before its own constructor, and modifiers reset return variables at every `_`. -- Optimizer **`runs`** (default 200) is the expected number of executions per opcode: lower favors deployment cost, higher favors call cost. +- [Optimizer `runs`](https://docs.soliditylang.org/en/latest/internals/optimizer.html) (default 200) is the expected number of executions per opcode: lower favors deployment cost, higher favors call cost. ### EVM version targeting -- **`evmVersion`** picks the target hard fork; the default moved to `shanghai` in 0.8.20 (emits **`PUSH0`**), `cancun` in 0.8.25, `prague` in 0.8.30, and `osaka` in 0.8.31. +- [`evmVersion`](https://docs.soliditylang.org/en/latest/using-the-compiler.html#setting-the-evm-version-to-target) picks the target hard fork; the default moved to `shanghai` in 0.8.20 (emits `PUSH0`, [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855)), `cancun` in 0.8.25, `prague` in 0.8.30, and `osaka` in 0.8.31. - Code with an opcode the target chain lacks fails to deploy — `PUSH0` broke L2 deployments in 2023; set `evmVersion` to the oldest chain you target. -- Pin the pragma (`pragma solidity 0.8.37;`) in contracts you deploy; a floating `^0.8.0` suits libraries. +- Pin the [pragma](https://docs.soliditylang.org/en/latest/layout-of-source-files.html#version-pragma) (`pragma solidity 0.8.37;`) in contracts you deploy; a floating `^0.8.0` suits libraries. ### Testing with Foundry -- Tests are Solidity contracts; **cheatcodes** (`vm.prank`, `vm.warp`, `vm.deal`, `vm.expectRevert`) set the caller, time, balances, and expected failures. -- **Fuzz tests** take random arguments (256 runs by default); `bound()` constrains them. -- **Invariant tests** call random sequences of handler functions and check properties after each (total supply = sum of balances). -- Fork tests (`--fork-url`) run against live chain state. +- Tests are Solidity contracts; [cheatcodes](https://www.getfoundry.sh/reference/cheatcodes/overview) (`vm.prank`, `vm.warp`, `vm.deal`, `vm.expectRevert`) set the caller, time, balances, and expected failures. +- [Fuzz tests](https://www.getfoundry.sh/forge/testing) take random arguments (256 runs by default); [`bound()`](https://www.getfoundry.sh/reference/forge-std/bound) constrains them. +- [Invariant tests](https://www.getfoundry.sh/guides/invariant-testing) call random sequences of handler functions and check properties after each (total supply = sum of balances). +- [Fork tests](https://www.getfoundry.sh/guides/fork-testing) (`--fork-url`) run against live chain state. ### Static analysis and formal verification -- **Slither** flags known bug patterns (reentrancy, shadowing, uninitialized storage) in seconds. -- Symbolic and formal tools (Halmos, Certora, the built-in SMTChecker) prove properties for all inputs, not just sampled ones. +- [Slither](https://github.com/crytic/slither) flags known bug patterns (reentrancy, shadowing, uninitialized storage) in seconds. +- Symbolic and formal tools ([Halmos](https://github.com/a16z/halmos), [Certora](https://docs.certora.com/), the built-in [SMTChecker](https://docs.soliditylang.org/en/latest/smtchecker.html)) prove properties for all inputs, not just sampled ones. ## Inline assembly ### Memory layout -- `0x00`–`0x3f` is scratch space for hashing, **`0x40` holds the free memory pointer**, `0x60` is the zero slot; allocation starts at **`0x80`** — hence bytecode starting `6080604052`. +- `0x00`–`0x3f` is scratch space for hashing, `0x40` holds the [free memory pointer](https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html), `0x60` is the zero slot; allocation starts at `0x80` — hence bytecode starting `6080604052`. - Memory is never freed within a call: Solidity only bumps the free memory pointer. ### Assembly gotchas -- No overflow or bounds checks, and values narrower than 256 bits may carry **dirty upper bits** — mask them. -- Mark blocks **`assembly ("memory-safe")`** (0.8.13) so via-IR can still move variables to memory. -- `return` and `revert` in assembly end the whole call, not just the current function. -- Storage variables expose `.slot` and `.offset`; calldata arrays expose `.offset` and `.length`. +- No overflow or bounds checks, and values narrower than 256 bits may carry [dirty upper bits](https://docs.soliditylang.org/en/latest/internals/variable_cleanup.html) — mask them. +- Mark blocks [`assembly ("memory-safe")`](https://docs.soliditylang.org/en/latest/assembly.html#memory-safety) (0.8.13) so via-IR can still move variables to memory. +- `return` and `revert` in [inline assembly](https://docs.soliditylang.org/en/latest/assembly.html) end the whole call, not just the current function. +- Storage variables expose [`.slot` and `.offset`](https://docs.soliditylang.org/en/latest/assembly.html#access-to-external-variables-functions-and-libraries); calldata arrays expose `.offset` and `.length`. diff --git a/docs/topics/typescript.md b/docs/topics/typescript.md index a1ff83f..df15dd4 100644 --- a/docs/topics/typescript.md +++ b/docs/topics/typescript.md @@ -6,162 +6,164 @@ What experienced TypeScript engineers forget before an interview, grouped by sub ### Excess property checks -- Only **fresh object literals** get excess-property errors: `const p: Point = { x: 1, y: 2, z: 3 }` fails. +- Only fresh object literals get [excess-property errors](https://www.typescriptlang.org/docs/handbook/2/objects.html#excess-property-checks): `const p: Point = { x: 1, y: 2, z: 3 }` fails. - The same literal assigned to a variable first, then to `Point`, passes — the check never runs. ### `any`, `unknown`, `never` -- **`any`** turns checking off in both directions; **`unknown`** accepts anything but must be narrowed before use. -- **`never`** is the empty type — the result of exhaustive narrowing and of functions that never return. +- [`any`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any) turns checking off in both directions; [`unknown`](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown) accepts anything but must be narrowed before use. +- [`never`](https://www.typescriptlang.org/docs/handbook/2/functions.html#never) is the empty type — the result of exhaustive narrowing and of functions that never return. ### `{}`, `object`, and index signatures -- **`{}` means any non-nullish value** — strings and numbers included — not "empty object"; `Object` behaves the same. -- `object` is any non-primitive; `Record` is a truly empty object. -- With an index signature (`Record`), every key reads as present and typed `T` unless **`noUncheckedIndexedAccess`** is on. +- `{}` means any non-nullish value — strings and numbers included — not "empty object"; `Object` behaves the same. +- [`object`](https://www.typescriptlang.org/docs/handbook/2/functions.html#object) is any non-primitive; `Record` is a truly empty object. +- With an [index signature](https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures) (`Record`), every key reads as present and typed `T` unless [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess) is on. ### Function assignability -- A function returning a value is assignable to a **`void`-returning** function type, so `arr.forEach(x => out.push(x))` compiles and the result is ignored. -- A function with **fewer parameters** is assignable to one with more: callbacks may ignore arguments (`arr.map(x => x * 2)`). +- A function returning a value is assignable to a [`void`-returning](https://www.typescriptlang.org/docs/handbook/2/functions.html#return-type-void) function type, so `arr.forEach(x => out.push(x))` compiles and the result is ignored. +- A function with [fewer parameters](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#comparing-two-functions) is assignable to one with more: callbacks may ignore arguments (`arr.map(x => x * 2)`). - Only the contextual case is relaxed: a function declared with a `void` return type still can't return a value. ### `interface` vs `type` -- `interface` supports **declaration merging** (reopening adds members) and gives clearer error messages for object shapes. -- `type` can alias unions, tuples, and mapped/conditional types, which `interface` can't express. +- `interface` supports [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) (reopening adds members) and gives clearer error messages for object shapes. +- [`type`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces) can alias unions, tuples, and mapped/conditional types, which `interface` can't express. ### Variance annotations -- Generic types are checked by structure, so variance is inferred; **`in`**/**`out`** annotations (`interface Producer`, 4.7) declare contravariance/covariance explicitly — faster checks and clearer errors. -- Arrays are treated **covariantly** (`Dog[]` assignable to `Animal[]`) even though writes make that unsound. +- Generic types are checked by structure, so variance is inferred; [`in`/`out` annotations](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters) (`interface Producer`, 4.7) declare contravariance/covariance explicitly — faster checks and clearer errors. +- Arrays are treated covariantly (`Dog[]` assignable to `Animal[]`) even though writes make that [unsound](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness). ### Method bivariance -- Under `strictFunctionTypes`, function-typed properties (`f: (x: T) => void`) check parameters **contravariantly**. -- Method shorthand (`f(x: T): void`) stays **bivariant** on purpose — unsound, but keeps common override patterns compiling. +- Under [`strictFunctionTypes`](https://www.typescriptlang.org/tsconfig/#strictFunctionTypes), function-typed properties (`f: (x: T) => void`) check parameters contravariantly. +- Method shorthand (`f(x: T): void`) stays [bivariant](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types) on purpose — unsound, but keeps common override patterns compiling. ## Narrowing ### Control-flow narrowing -- `typeof`, `instanceof`, `in`, equality, and truthiness checks narrow a variable inside the branch where they're provably true. -- **Discriminated unions**: a shared literal tag (`kind`) lets `switch (x.kind)` narrow each case. -- **Exhaustiveness**: in the `default` branch, assign the value to a `never` variable; adding a new union member then fails to compile until handled. +- `typeof`, `instanceof`, `in`, equality, and truthiness checks [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) a variable inside the branch where they're provably true. +- [Discriminated unions](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions): a shared literal tag (`kind`) lets `switch (x.kind)` narrow each case. +- [Exhaustiveness](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking): in the `default` branch, assign the value to a `never` variable; adding a new union member then fails to compile until handled. ### Custom type guards -- A **type predicate** (`x is Fish`) or an **`asserts x is T`** function packages narrowing the compiler can't infer. -- **Inferred type predicates** (5.5): a boolean-returning function that already narrows gets `x is T` automatically, e.g. `arr.filter(x => x !== undefined)`. +- A [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) (`x is Fish`) or an [`asserts x is T`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions) function packages narrowing the compiler can't infer. +- [Inferred type predicates](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#inferred-type-predicates) (5.5): a boolean-returning function that already narrows gets `x is T` automatically, e.g. `arr.filter(x => x !== undefined)`. ### `as` vs `satisfies` vs `!` -- **`x as T`** is an unchecked assertion — it only rejects conversions between unrelated types (bypass: `as unknown as T`). -- **`satisfies T`** (4.9) checks without changing the inferred type; **`x!`** strips `null`/`undefined` with no runtime check. +- [`x as T`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) is an unchecked assertion — it only rejects conversions between unrelated types (bypass: `as unknown as T`). +- [`satisfies T`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) (4.9) checks without changing the inferred type; [`x!`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix-) strips `null`/`undefined` with no runtime check. ## Type-level programming ### Conditional types -- Conditional types **distribute** over a naked type parameter: `C` = `C | C`; wrap as `[T] extends [U]` to disable it. -- **`infer`** extracts a part of a type, powering `ReturnType` and `Parameters`. +- [Conditional types distribute](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) over a naked type parameter: `C` = `C | C`; wrap as `[T] extends [U]` to disable it. +- [`infer`](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types) extracts a part of a type, powering [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) and `Parameters`. ### Mapped and template literal types -- Mapped types (`{ [K in keyof T]: T[K] }`) transform each property; `as` remaps keys; `+`/`-` add or strip `readonly` and `?`. -- Template literal types build string unions: `` `on${Capitalize}` ``. +- [Mapped types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) (`{ [K in keyof T]: T[K] }`) transform each property; [`as` remaps keys](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as); [`+`/`-`](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers) add or strip `readonly` and `?`. +- [Template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) build string unions: `` `on${Capitalize}` ``. ### Inference controls -- **`const` type parameters** (5.0) infer literal types without the caller writing `as const`. -- **`NoInfer`** (5.4) excludes a position from inference, so a default argument can't widen `T`. +- [`const` type parameters](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#const-type-parameters) (5.0) infer literal types without the caller writing `as const`. +- [`NoInfer`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type) (5.4) excludes a position from inference, so a default argument can't widen `T`. ### Utility types on unions -- **`Omit` and `Pick` aren't distributive**: `Omit` keeps only keys common to both, collapsing the union. +- [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) and `Pick` aren't distributive: `Omit` keeps only keys common to both, collapsing the union. - Write a distributive version: `type DistOmit = T extends unknown ? Omit : never`. -- `keyof (A | B)` is only the shared keys; `Partial` and `Readonly` are **shallow**. +- `keyof (A | B)` is only the shared keys; [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) and `Readonly` are shallow. ### Branded types -- Types are compatible by **shape**, not declared name (structural typing), so `UserId` and `OrderId` are both plain `string`s; **brand** them for nominal-like safety: `type UserId = string & { readonly __brand: 'UserId' }`. +- Types are compatible by shape, not declared name ([structural typing](https://www.typescriptlang.org/docs/handbook/type-compatibility.html)), so `UserId` and `OrderId` are both plain `string`s; brand them for nominal-like safety: `type UserId = string & { readonly __brand: 'UserId' }`. - Create values only through a validating function (`asUserId(s)`); the brand has no runtime cost. ## Compilation and tooling ### Type erasure -- Types, interfaces, and type-only imports are erased — **zero runtime footprint**. -- Exceptions that emit runtime code: **`enum`s** (a `const enum` is inlined instead), `namespace`s with values, parameter properties, and legacy decorators. +- Types, interfaces, and type-only imports are [erased](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#erased-types) — zero runtime footprint. +- Exceptions that emit runtime code: [`enum`s](https://www.typescriptlang.org/docs/handbook/enums.html) (a `const enum` is inlined instead), [`namespace`s](https://www.typescriptlang.org/docs/handbook/namespaces.html) with values, [parameter properties](https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties), and [legacy decorators](https://www.typescriptlang.org/docs/handbook/decorators.html). ### Transpile-only builds -- esbuild, swc, and Babel strip types **file by file** without type checking — fast, but they emit JS from type-invalid code. -- **`isolatedModules`** flags constructs a single-file transpiler can't handle; **`verbatimModuleSyntax`** requires explicit `import type`, so elision needs no type analysis. +- [esbuild](https://esbuild.github.io/content-types/#typescript-caveats), [swc](https://swc.rs/), and [Babel](https://babeljs.io/docs/babel-preset-typescript) strip types file by file without type checking — fast, but they emit JS from type-invalid code. +- [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) flags constructs a single-file transpiler can't handle; [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) requires explicit `import type`, so elision needs no type analysis. ### Node type stripping -- Node runs `.ts` files by stripping types, on by default **since Node 22.18 / 23.6**. -- **`erasableSyntaxOnly`** (**5.8**) forbids syntax that can't simply be erased (enums, parameter properties, value namespaces); Node still ignores tsconfig (`paths`, JSX) and needs explicit `.ts` import extensions. +- Node runs `.ts` files by [stripping types](https://nodejs.org/api/typescript.html#type-stripping), on by default since Node 22.18 / 23.6. +- [`erasableSyntaxOnly`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option) (5.8) forbids syntax that can't simply be erased (enums, parameter properties, value namespaces); Node still ignores tsconfig (`paths`, JSX) and needs explicit `.ts` import extensions. ### Module resolution -- **`moduleResolution: bundler`** mirrors bundlers (extensionless imports allowed); **`nodenext`** mirrors Node exactly, requiring file extensions in ESM. -- `node10` (the old `node`) ignores `package.json` `exports` and is deprecated in 6.0. +- [`moduleResolution: bundler`](https://www.typescriptlang.org/tsconfig/#moduleResolution) mirrors bundlers (extensionless imports allowed); `nodenext` mirrors Node exactly, requiring file extensions in ESM. +- `node10` (the old `node`) ignores `package.json` `exports`; it is [deprecated in 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#deprecated---moduleresolution-node-aka---moduleresolution-node10) and removed in 7.0. ### TypeScript 6.0 defaults -- **TypeScript 6.0** (March 2026), the last JS-based release, turns **`strict` on by default** and defaults `module` to `esnext` and `target` to `es2025`. -- **`types` defaults to `[]`**: global `@types` packages are no longer auto-included — add `"types": ["node"]` or get "Cannot find name 'process'". -- Deprecates `target: es5`, `moduleResolution: node10`, `baseUrl`, `outFile`, and AMD/UMD/SystemJS output; 7.0 removes them. +- [TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/) (March 2026), the last JS-based release, turns [`strict`](https://www.typescriptlang.org/tsconfig/#strict) on by default and defaults `module` to `esnext` and `target` to `es2025`. +- [`types`](https://www.typescriptlang.org/tsconfig/#types) defaults to `[]`: global `@types` packages are no longer auto-included — add `"types": ["node"]` or get "Cannot find name 'process'". +- [Deprecates](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#breaking-changes-and-deprecations-in-typescript-60) `target: es5`, `moduleResolution: node10`, `baseUrl`, `outFile`, and AMD/UMD/SystemJS output — errors unless `"ignoreDeprecations": "6.0"`; 7.0 removes them. ### TypeScript 7 native compiler -- **TypeScript 7** (2026) ships the Go port of the compiler (`tsgo` in preview): roughly **10×** faster builds with parallel checking. +- [TypeScript 7.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) (July 2026) is the Go port of the compiler, still installed as `typescript` with a `tsc` binary (previews shipped as `tsgo` in `@typescript/native-preview`). +- Full builds are typically 8–12× faster than 6.0: parsing, emit, and [type checking run in parallel](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#type-checker-parallelization) (`--checkers`, default 4). +- It ships no compiler API yet (planned for 7.1), so tools built on the 6.0 API (Vue, Svelte, and Angular template checking) still need 6.0 alongside. ## Strictness flags ### The `strict` family -- **`strict`** enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`, and more — on by default since 6.0. -- Without **`strictNullChecks`**, `null`/`undefined` are assignable to every type except `never`. +- [`strict`](https://www.typescriptlang.org/tsconfig/#strict) enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`, and more — on by default since 6.0. +- Without [`strictNullChecks`](https://www.typescriptlang.org/tsconfig/#strictNullChecks), `null`/`undefined` are assignable to every type except `never`. ### Flags outside `strict` -- **`noUncheckedIndexedAccess`**: `arr[i]` and `record[key]` become `T | undefined`. -- **`exactOptionalPropertyTypes`**: `x?: number` means "absent", no longer "absent or explicitly `undefined`". +- [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess): `arr[i]` and `record[key]` become `T | undefined`. +- [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes): `x?: number` means "absent", no longer "absent or explicitly `undefined`". ## Declarations and runtime features ### Enums and alternatives -- Numeric enums compile to a **bidirectional** object (name ↔ value); string enums map one way. -- **`const enum`** values are inlined, but break under `isolatedModules` when imported from declaration files — many teams use string-literal unions or `as const` objects instead. +- Numeric enums compile to a [bidirectional](https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings) object (name ↔ value); string enums map one way. +- [`const enum`](https://www.typescriptlang.org/docs/handbook/enums.html#const-enums) values are inlined, but [break under `isolatedModules`](https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls) when imported from declaration files — many teams use string-literal unions or `as const` objects instead. ### Declaration files and augmentation -- **`.d.ts`** files hold only types, describing JS libraries or shipping types beside compiled output. -- **`declare global`** and **module augmentation** (`declare module 'express' { interface Request { user?: User } }`) extend third-party types. +- [`.d.ts`](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) files hold only types, describing JS libraries or shipping types beside compiled output. +- [`declare global`](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation) and [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) (`declare module 'express' { interface Request { user?: User } }`) extend third-party types. ### Decorators -- **Standard decorators** (TC39, **5.0**) have no parameter decorators and no metadata by default. -- **`experimentalDecorators`** is the older, incompatible form still used by Angular and NestJS — check which one a codebase uses. +- [Standard decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) (TC39, 5.0) have no parameter decorators and no metadata by default. +- [`experimentalDecorators`](https://www.typescriptlang.org/tsconfig/#experimentalDecorators) is the older, incompatible form still used by Angular and NestJS — check which one a codebase uses. ## Typing patterns ### Runtime validation at boundaries - Types are erased: `JSON.parse` and `res.json()` return `any`, and env vars or `as` casts are unchecked, so data can violate its declared type at runtime. -- Parse untrusted input with a schema library (Zod, Valibot, ArkType) and **derive the type from the schema** (`type User = z.infer`), so the two can't drift. -- Type unparsed input as **`unknown`**, not `any`, so it must be validated or narrowed before use. +- Parse untrusted input with a schema library ([Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [ArkType](https://arktype.io/)) and derive the type from the schema (`type User = z.infer`), so the two can't drift. +- Type unparsed input as [`unknown`](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown), not `any`, so it must be validated or narrowed before use. ### Overloads -- Several **overload signatures** followed by one implementation signature that callers can't see. -- Resolution picks the **first matching** signature top-down — list specific before general; prefer generics or a union parameter when they're precise enough. +- Several [overload signatures](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads) followed by one implementation signature that callers can't see. +- Resolution picks the [first matching](https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#ordering) signature top-down — list specific before general; prefer generics or a union parameter when they're precise enough. ### Readonly and `as const` -- `readonly T[]` blocks `push`/`splice` at compile time; **`as const`** narrows a literal to its most specific type and makes it deeply `readonly` — but not arrays/objects it only references. +- [`readonly T[]`](https://www.typescriptlang.org/docs/handbook/2/objects.html#the-readonlyarray-type) blocks `push`/`splice` at compile time; [`as const`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) narrows a literal to its most specific type and makes it deeply `readonly` — but not arrays/objects it only references. - Both are compile-time only — nothing is frozen at runtime.