JavaScript — Reference

Source: https://tc39.es/ecma262/

JavaScript

  • Created: 1995 by Brendan Eich at Netscape (originally “Mocha”, then “LiveScript”); standardized as ECMAScript by Ecma International TC39
  • Latest stable: ECMAScript 2025 ratified mid-2025; ES2026 in flight; latest editor’s draft tracks https://tc39.es/ecma262/. Node.js 26 (Current, 2026-05-05), Node.js 24 LTS “Krypton” (2025-05-06)
  • Paradigms: multi-paradigm — prototype-based OO, functional, imperative, event-driven; classes are syntactic sugar over prototypes
  • Typing: dynamic, weakly typed (with implicit coercion). Static layer via TypeScript or JSDoc.
  • Memory: garbage collected — generational, mark-sweep + scavenger; per-engine details (V8, SpiderMonkey, JavaScriptCore)
  • Compilation: JIT-compiled in modern engines (V8 Ignition+TurboFan/Maglev, SpiderMonkey IonMonkey/WarpMonkey, JSC LLInt+Baseline+DFG+FTL). Originally interpreted.
  • Primary domains: browser frontend, server-side (Node.js, Deno, Bun), desktop (Electron, Tauri), mobile (React Native, Capacitor), serverless / edge, embedded scripting
  • Official docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript

At a glance

  • Standard: ECMA-262, annual yearly snapshots since ES2015. Living spec at https://tc39.es/ecma262/.
  • Engines: V8 (Chrome, Node.js, Deno, Edge), SpiderMonkey (Firefox), JavaScriptCore / Nitro (Safari, Bun uses JSC), Hermes (React Native), QuickJS (embedded).
  • Runtimes: browsers, Node.js, Deno (TS-first, secure-by-default), Bun (Zig + JSC, npm-compatible, fast), Cloudflare Workers (V8 isolates), edge platforms (Vercel, Netlify, Fastly Compute).
  • Governance: TC39 with stage process 0-4; Stage 4 = in the next yearly snapshot.

Getting started

Install:

  • Browsers: built in.
  • Node.js: https://nodejs.org/ — installer, or version manager: nvm (POSIX), fnm (Rust), volta, nvs (Windows). Bun and Deno include their own version managers (bun upgrade, deno upgrade).
  • For Node 24+ projects, prefer Corepack to pin pnpm/yarn versions.

Hello world (browser):

<script>console.log("Hello, world!");</script>

Hello world (Node.js):

// hello.mjs
console.log("Hello, world!");

Run: node hello.mjs.

Project layout (Node, ESM):

myapp/
  package.json          # "type": "module"
  src/
    index.js
  test/
    index.test.js
  node_modules/
  package-lock.json

Package managers:

  • npm (bundled with Node), pnpm (content-addressed store, fast), yarn (Berry / v4, PnP), bun (built-in).
  • Lockfiles: package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lockb.

REPL / playground: node or node --experimental-repl-await. Browser DevTools console. Online: https://playcode.io, https://stackblitz.com, MDN’s “Try it” boxes.

Basics

Primitives: number (IEEE 754 double, 53-bit safe int), bigint (123n), string (UTF-16), boolean, symbol, undefined, null. Plus object (incl. arrays, functions, dates, maps).

Variables / scope:

const x = 10;       // block-scoped, immutable binding (value can mutate if object)
let y = 1;          // block-scoped, reassignable
var z = 2;          // function-scoped, hoisted (legacy — avoid)

Closures capture by reference; this is dynamic (fn.call, arrow functions inherit lexical this).

Control flow: if/else, for, for...of (iterables), for...in (enumerable keys, avoid for arrays), while, do/while, switch, try/catch/finally, throw.

Functions:

function add(a, b = 0, ...rest) { return a + b + rest.length; }
const sub = (a, b) => a - b;       // arrow — no own `this`, no `arguments`
async function fetchJson(url) { return (await fetch(url)).json(); }
function* range(n) { for (let i = 0; i < n; i++) yield i; }  // generator

Strings: template literals (backtick) with ${expr}, tagged templates, raw strings via String.raw.

const name = "world";
console.log(`Hello, ${name}!`);

Built-in collections: Array, Object (string-keyed), Map, Set, WeakMap, WeakSet, WeakRef, Date, RegExp, typed arrays (Uint8Array, Float32Array, etc.), ArrayBuffer, SharedArrayBuffer, DataView.

Intermediate

Types & inference: none natively. JSDoc + // @ts-check gives editor-grade typing without TS. Most modern Node/Deno projects use TypeScript directly. Type erasure proposal (Stage 1) would let JS engines parse-and-strip TS-style annotations.

Modules: ESM (import/export, .mjs or "type":"module") is the standard. CommonJS (require/module.exports) is legacy but pervasive in Node. Dynamic import() returns a Promise. Import attributes for non-JS resources: import data from "./d.json" with { type: "json" }.

Errors: throw any value, but throw Error subclasses (TypeError, RangeError, SyntaxError, custom class MyError extends Error). try/catch (err)err is unknown style; check with instanceof. AggregateError for Promise.any. Error.cause for chaining.

Concurrency:

  • Single-threaded event loop per realm. Tasks vs microtasks (queueMicrotask).
  • Promise (.then / await / Promise.all / Promise.allSettled / Promise.race / Promise.any / Promise.try).
  • Async iterators (for await ... of) and async generators.
  • Workers: Worker (browsers), worker_threads (Node), Web Worker / Service Worker, SharedWorker.
  • SharedArrayBuffer + Atomics for shared-memory parallelism.
  • AbortController / AbortSignal for cancellation.

File I/O / networking:

  • Browser: fetch, WebSocket, XMLHttpRequest (legacy), IndexedDB, OPFS.
  • Node: node:fs/promises, node:fs, node:net, node:http, node:https, node:dgram, plus a built-in fetch since 18, node:test runner, node:sqlite (24+).
  • Deno/Bun: web-standard APIs first (fetch, Request, Response).

Stdlib highlights: Intl (i18n: Intl.NumberFormat, Intl.DateTimeFormat, Intl.Segmenter, Intl.Collator, Intl.DurationFormat ES2024), URL / URLPattern, TextEncoder/TextDecoder, crypto.subtle (Web Crypto), structuredClone, Temporal (Stage 3, shipping in Firefox 139+/Safari TP/Node 24+ behind flag — finally fixes Date).

ES2024 / ES2025 highlights:

  • Promise.withResolvers() — get { promise, resolve, reject } without the new-Promise dance.
  • Array.fromAsync(asyncIterable) — sibling of Array.from for async iteration.
  • Object.groupBy(items, keyFn) / Map.groupBy(items, keyFn) — built-in groupby.
  • Set methods: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom.
  • await using / using (Stage 3, ES2026 likely) — explicit resource management; like Python with. using db = await connect() calls db[Symbol.dispose]() (or async variant) at scope exit.
  • Iterator helpers (ES2025) — .map() / .filter() / .take() / .drop() / .flatMap() / .reduce() / .toArray() on Iterator.prototype. Lazy by default.
  • Float16Array (ES2025) — half-precision typed array for ML / WebGPU interop.
  • RegExp /v flag (ES2024) — set notation, intersection, set difference within character classes.
  • JSON modules + import attributes (ES2025): import data from "./d.json" with { type: "json" }.
  • Top-level await in ES modules — load configs / await connections before exports settle.
  • Decorators Stage 3 (shipping behind flags) — see God Mode below.

Advanced

Memory model & GC:

  • V8: generational (young / old), Scavenger for young, Mark-Compact for old, Orinoco concurrent / parallel collector. Pointer compression on 64-bit (4-byte tagged pointers within a 4 GB cage — 30-40% heap size reduction). Conservative stack scanning since 11.2. Major GC pauses typically 1-10 ms in production Node.
  • Tune Node: --max-old-space-size=4096 (heap cap in MB), --max-semi-space-size (new-gen size; bigger = fewer scavenges, more memory), --gc-interval, --expose-gc for global.gc(). Container-aware since Node 12 — respects cgroup memory limits.
  • Inspect heap: --inspect, Chrome DevTools Heap Snapshot, clinic.js, 0x (flamegraphs), heapdump, memwatch-next.
  • V8 tier-up pipeline (2024-26): Ignition (bytecode interpreter) → Sparkplug (non-optimizing baseline JIT, fastest tier-up) → Maglev (mid-tier optimizing JIT, enabled by default in Chrome 117+) → TurboFan (top-tier optimizing JIT, Sea-of-Nodes IR). Liftoff handles WASM warmup before TurboFan. Each tier trades compile time vs steady-state perf.

Concurrency deep dive:

  • Microtask queue (Promises) drains between macrotasks.
  • Atomics.wait/notify for futex-style sync over SharedArrayBuffer.
  • Worker pools via piscina (Node).
  • AsyncContext (Stage 3 TC39) — async-aware context propagation.
  • Node node:cluster for multi-process; worker_threads for CPU-bound.

FFI / interop:

  • Node: N-API / node-addon-api (stable C ABI), Node-API with napi-rs (Rust), neon (Rust), legacy nan. WASI for portable native modules.
  • Deno: Deno.dlopen direct FFI to .so/.dll/.dylib.
  • Bun: bun:ffi direct FFI.
  • Browser: WebAssembly (WebAssembly.instantiate), Emscripten, wasm-bindgen (Rust).

Reflection / introspection: Object.keys/getOwnPropertyDescriptors/getPrototypeOf/getOwnPropertyNames, Reflect.*, Symbol.iterator/asyncIterator/hasInstance/toPrimitive, instanceof, typeof, Function.prototype.toString returns source.

Performance tuning:

  • V8 inspection: --trace-opt, --trace-deopt, --prof, --prof-process, --print-bytecode, --allow-natives-syntax then %OptimizeFunctionOnNextCall(fn).
  • Tools: Chrome DevTools Performance / Memory, node --inspect, clinic.js doctor/flame/bubbleprof, 0x, autocannon (HTTP load), mitata / tinybench (microbenchmarks).
  • Avoid hidden-class polymorphism — initialize objects with the same property order; don’t add/delete props on hot paths.

God mode

Proxy & Reflect: intercept any object operation.

const audited = new Proxy(target, {
  get(t, p, r) { console.log("get", p); return Reflect.get(t, p, r); },
  set(t, p, v, r) { console.log("set", p, v); return Reflect.set(t, p, v, r); },
});

Underpins Vue 3 reactivity, immer, MobX, observable proxies.

Iteration & generator protocols: any object implementing [Symbol.iterator]() or [Symbol.asyncIterator]() works in for...of. Generators support .return() / .throw() for cooperative cancellation.

V8 internals: hidden classes (Maps), inline caches (ICs), CodeStubAssembler / Torque for builtins, Sea-of-Nodes IR. --print-code dumps machine code for hot functions. Sparkplug → Maglev → TurboFan tier-up.

AsyncContext: propagate context across await boundaries without monkeypatching. Replaces domain / async-local-storage hacks.

SharedArrayBuffer + Atomics: the only path to true shared-memory parallelism. Requires COOP/COEP cross-origin isolation in browsers. Atomics.waitAsync is non-blocking.

BigInt + Atomics: BigInt64Array works with Atomics; useful for lock-free 64-bit counters and futexes from WASM.

Eval scope tricks: direct eval("x = 1") sees / mutates caller’s local scope; indirect (0, eval)("x = 1") runs in global scope. new Function(...) always global. Both are usually the wrong answer.

Realms / iframes: different realms have different Array, so arr instanceof Array can lie. Use Array.isArray(arr).

Decorators (Stage 3, ES2023+ shipping):

function logged(value, { kind, name }) {
  if (kind === "method") return function (...args) { console.log(name); return value.apply(this, args); };
}
class C { @logged greet() {} }

Engine embedding: V8 embedder API (v8::Isolate, v8::Context), JSC API (JSContext, JSValue), QuickJS for tiny embeds, Hermes for AOT-compiled bytecode (React Native).

Bytecode:

  • V8 Ignition: stack-based, viewable via --print-bytecode.
  • JSC LLInt: low-level interpreter, also visible.
  • Hermes: AOT to .hbc bytecode, ship that instead of JS.

Runtime landscape (2024-2026):

RuntimeEngineDifferentiatorUse when
Node.js 24 LTS / 26V8npm ecosystem, deepest API surface, built-in test runner + --watch + --env-file + node:sqlite (24+)Anything that needs broad pkg compat
Bun 1.2+JSC + ZigNative TS, all-in-one (runtime + bundler + test + pm), 3-10x faster install, native SQLite + Redis + S3 in 1.2+, full Node compatGreenfield, dev speed matters
Deno 2.0+V8 + RustSecure-by-default (perms), npm compat (Oct 2024 v2), native TS, web-standard APIs, JSR registryScripts, edge, security-first
Cloudflare WorkersV8 isolates<1ms cold start, runs at 300+ POPs, no Node API surfaceEdge functions
Hermes(custom)AOT bytecode, no JIT, tiny runtimeReact Native (default since 0.70)
QuickJS / txikiQuickJS<1MB embeddable interpreterEmbedded scripting

Build tooling (2024-2026):

  • Vite 6+ — dominant dev server; Rolldown (Rust port of Rollup) replacing Rollup as the production bundler in Vite 6.0+. ~5-10x faster prod builds.
  • esbuild — Go-based, the speed standard, still used inside many tools.
  • Rspack (ByteDance) — Rust port of webpack, drop-in compatibility, used by ByteDance + Microsoft.
  • Turbopack (Vercel) — Rust, incremental, Next.js default in 15+ (stable for dev, beta for build through early 2026).
  • swc — Rust-based TS/JS compiler, used by Next.js, Deno, Parcel.
  • oxc / oxlint — Boshen’s Rust toolchain; oxlint is 50-100x faster than ESLint on the same rule subset.
  • Biome (formerly Rome) — Rust, all-in-one fmt + lint, near-Prettier compatibility, 25x faster.
  • Bun’s bundler — built in, JSC + Zig, comparable speed to esbuild.

Framework state (2024-2026):

  • React 19 (Dec 2024 stable): Server Components GA, Actions / useActionState / useFormStatus, use(promise) hook, native ref-as-prop, <Context> directly as provider, document metadata support. Compiler (react-compiler) auto-memoizes — replaces most useMemo/useCallback/memo.
  • Vue 3.5+: reactive props destructuring stable, useTemplateRef, deferred teleport, generic components.
  • Svelte 5 (Oct 2024): runes ($state, $derived, $effect) — universal reactivity model, used in components AND .svelte.js modules.
  • SolidJS 1.9+ — fine-grained reactivity, no VDOM; SolidStart 1.0 (2024) ships.
  • Qwik 2.0 — resumability instead of hydration; partnered with Builder.io.
  • Astro 5+ (Nov 2024) — Server Islands, Content Layer API, server-rendered partial hydration.
  • Remix → React Router 7 (Nov 2024) — merged, framework mode = Remix, library mode = RR.
  • Next.js 15 — async request APIs, React 19 baseline, Turbopack stable for dev, stable Partial Prerendering.
  • Nuxt 3.14+ — Nitro 2.10, hybrid rendering, shared types across server/client/edge.
  • TanStack Start — type-safe full-stack on TanStack Router (beta through 2026).

Idioms & style

  • Naming: camelCase for vars/functions, PascalCase for classes/constructors, SCREAMING_SNAKE for module-level constants, _private (convention), #truly-private (class private fields, ES2022).
  • Formatters: Prettier (de facto), Biome (Rust, all-in-one fmt+lint, fast), dprint.
  • Linters: ESLint (v9+ flat config), Biome, oxlint (Rust, fast subset). Common configs: eslint-config-airbnb, @typescript-eslint, eslint-plugin-import.
  • Idiomatic patterns:
    • const by default, let only when reassigning, never var.
    • === / !== over == / != (avoid coercion).
    • Destructuring + default params: function f({ a = 1, b } = {}) {}.
    • Async/await over raw Promise chains.
    • Optional chaining ?. and nullish coalescing ?? over && / || for null-vs-falsy distinctions.
    • Module side effects only when intentional; prefer pure exports.
    • Avoid class when a closure-returning factory is enough.
  • What reviewers flag: var, missing await, floating Promises (no-floating-promises rule), JSON.parse(JSON.stringify(x)) for cloning (use structuredClone), mutating shared state in arrow callbacks, broad catch (e) {}.

Ecosystem

DomainTools
FrontendReact, Vue, Svelte / SvelteKit, SolidJS, Angular, Qwik, Astro, Lit, Preact
Meta-frameworksNext.js, Nuxt, SvelteKit, Remix / React Router 7, Astro, Qwik City
ServerExpress, Fastify, Koa, Hono, NestJS, AdonisJS, ElysiaJS (Bun)
Build / bundleVite (Rolldown-based 2026), webpack, esbuild, Rolldown, Turbopack, Parcel
Type-aware buildtsc, swc, Babel, oxc
TestVitest, Jest, node:test, Mocha, Playwright (e2e), Cypress, Testing Library
StateZustand, Redux Toolkit, Jotai, MobX, Pinia (Vue), Effector
ORM / DBPrisma, Drizzle, Kysely, TypeORM, MikroORM, Mongoose
MobileReact Native, Expo, Capacitor, NativeScript
DesktopElectron, Tauri (Rust shell, JS frontend), Neutralino
Notable usersGoogle (V8, Angular), Meta (React, RN, Hermes), Netflix, Microsoft (TS, VS Code), Vercel, Stripe

Performance & profiling deep dive

  • Microbenchmarks: mitata (Bun/Node, sub-ns precision), tinybench, benchmark.js (legacy). Always run on --predictable V8 builds for low noise.
  • Load testing: autocannon (HTTP, Node-based, 100k+ req/s), k6 (Go, scriptable in JS), wrk2, oha (Rust). Bombardier replaced wrk for many teams.
  • Flame graphs: 0x (Node, drop-in flamegraphs), clinic.js (doctor / flame / bubbleprof for async hot spots), speedscope (web viewer for any sampling profile), node --cpu-prof + Chrome DevTools.
  • Hidden-class polymorphism: V8 builds a tree of “Maps” (hidden classes) per property-add sequence. Initialize all props in the constructor, in the same order, with the same types — keeps inline caches monomorphic. Adding props after new creates a transition; deleting props falls to “dictionary mode” (~10x slower property access).
  • Megamorphic call sites: if a .method() call sees >4 receiver shapes, V8 gives up specializing. Profile with --trace-ic.
  • Avoid try/catch in hot loops pre-2024; Maglev + TurboFan now optimize through try in most cases, but deopts on uncommon throws can still hurt — measure.

Node.js modern features (24+)

  • Built-in node --watch (file watcher, 22+).
  • node --env-file=.env loads dotenv-style files natively.
  • node:test test runner with subtests, describe/it, snapshot, coverage (--experimental-test-coverage).
  • fetch / Request / Response / FormData / Blob global since 18, stable in 22+.
  • node --experimental-strip-types (22.6+) and --experimental-transform-types (23+) for native TS without compile step.
  • node:sqlite built-in (24+) — embedded SQLite, no native deps.
  • WebSocket client built in since 21.
  • Permission model (node --permission --allow-read=./data, 20+, experimental) — Deno-style sandboxing.
  • Single-executable applications (SEA, node --experimental-sea-config) — bundle Node + app into one binary.

Gotchas

  • == coercion: [] == false is true, 0 == "0" is true, null == undefined is true. Use ===.
  • typeof null === "object" — historical bug, never fixed.
  • NaN !== NaN — use Number.isNaN(x) or Object.is(x, NaN).
  • Floating point: 0.1 + 0.2 === 0.3 is false. Use BigInt or fixed-point for money.
  • this rebinding: lost when you pass a method as a callback. Bind it (fn.bind(this)) or use arrows.
  • for...in on arrays — iterates keys as strings, plus inherited enumerable props. Use for...of or forEach.
  • Hoisting: var and function declarations hoist; let/const are in the temporal dead zone until initialized.
  • Implicit globals: assignment to undeclared name in non-strict mode creates a global. Use "use strict" or modules (always strict).
  • Floating Promises: unhandled rejections crash Node by default in 22+. Always await or .catch().
  • Date is awful — month is 0-indexed, mutable, no timezone. Use Temporal (Stage 3) or date-fns / Luxon.
  • JSON limitations: no BigInt, no undefined, no functions, no circular refs, dates become strings.
  • Newcomers from Java/C#: no method overloading, no real privacy except #field, no compile-time type errors without TS.
  • Newcomers from Python: truthiness differs ("", 0, null, undefined, NaN, false are falsy; [] and {} are truthy). Object key order is “integer-like keys ascending, then string keys insertion-order.”
  • Number.MAX_SAFE_INTEGER = 2^53 - 1 — JSON parsers will silently lose precision on bigger ints. Use BigInt or string-encode ({"id":"12345678901234567"}).
  • Module caching: import and require cache by resolved path. Two import specifiers resolving differently (e.g., node_modules vs symlink) give you two separate instances of the same module — confusing for singletons.
  • Array(5) vs Array.of(5)Array(5) creates a length-5 hole-array; Array.of(5) creates [5]. new Array(5).map(f) doesn’t call f because holes are skipped; use Array.from({length: 5}, f).
  • Object.create(null) — true dictionary, no prototype, no toString collisions. Use for user-supplied keys.
  • Spread copies are shallow{...obj} and [...arr] only copy one level; nested objects share refs. Use structuredClone(x) for deep clone (supports cycles, Map/Set, typed arrays).
  • async functions always return a Promise — even async function f() { return 1 } returns Promise<1>. Forgetting await returns the Promise itself.
  • WeakMap keys must be objects (and now also Symbols since ES2023) — primitives won’t work. WeakRef for general weak references; finalizer runs in FinalizationRegistry.

Node.js LTS / release timeline

VersionCodenameReleaseLTS untilHighlights
18HydrogenApr 2022Apr 2025 (EOL)Fetch / FormData built-in, prefix-only built-in node:
20IronApr 2023Apr 2026Test runner stable, single-executable apps
22JodApr 2024Apr 2027--watch flag, node:sqlite, --experimental-strip-types, V8 12.4 + Maglev
24KryptonMay 2025Apr 2028--experimental-transform-types (enums/decorators), URLPattern global, npm 11
26(TBD)May 2026(Apr 2029)Bundled node:test updates, continuing TS work

Even-numbered releases promote to LTS in October of their release year; odd-numbered (Current only) are short-lived.

Async patterns + cancellation (modern)

// AbortController for fetch + custom async ops
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 5_000);
try {
  const res = await fetch(url, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") { /* timeout */ }
} finally { clearTimeout(t); }
 
// AbortSignal.timeout (ES2024+, Node 20+): one-liner
await fetch(url, { signal: AbortSignal.timeout(5_000) });
 
// AbortSignal.any() combines multiple cancellation sources
const any = AbortSignal.any([userCancel.signal, AbortSignal.timeout(5000)]);
 
// Promise.withResolvers (ES2024) — useful for queues / event-to-Promise bridges
const { promise, resolve, reject } = Promise.withResolvers();
emitter.once("ready", resolve);
emitter.once("error", reject);
return promise;

WebAssembly and browser advanced

  • WebAssembly runs in every modern browser; baseline since 2017. Compile from C/C++ (emscripten, Cheerp), Rust (wasm-bindgen + wasm-pack), Go (GOOS=js GOARCH=wasm / wasip1), AssemblyScript (TS-like), Zig, .NET (Blazor).
  • WASM threads (SharedArrayBuffer + atomics) require COOP/COEP cross-origin isolation headers. Enables true multi-core in the browser.
  • WebGPU (W3C stable in Chrome 113+, Firefox 141+, Safari 26+) — modern graphics + compute. Used by ML inference (transformers.js, WebLLM, Web-stable-diffusion).
  • OPFS (Origin Private File System) — true POSIX-like filesystem in browser; replaces IndexedDB for large structured local data.
  • Service Workers + Background Sync + Web Push + Background Fetch for offline-first PWAs.
  • WASI Preview 2 + Component Model — composable Wasm components across languages, running on Wasmtime, WasmEdge, Wasmer, Spin, fermyon, Fastly Compute@Edge.

Code examples — modern patterns

React 19 server actions + useActionState

"use server";
 
async function updateProfile(prev, formData) {
    const name = formData.get("name");
    try {
        await db.user.update({ where: { id: 1 }, data: { name } });
        return { ok: true, message: "Saved" };
    } catch (e) {
        return { ok: false, message: e.message };
    }
}
 
// Client component
"use client";
import { useActionState } from "react";
 
export function ProfileForm() {
    const [state, formAction, pending] = useActionState(updateProfile, null);
    return (
        <form action={formAction}>
            <input name="name" />
            <button disabled={pending}>{pending ? "Saving..." : "Save"}</button>
            {state?.message && <p>{state.message}</p>}
        </form>
    );
}

Hono / Bun edge HTTP server with type-safe RPC

import { Hono } from "hono";
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";
 
const app = new Hono()
    .post(
        "/users",
        zValidator("json", z.object({ name: z.string(), age: z.number() })),
        (c) => {
            const { name, age } = c.req.valid("json");
            return c.json({ id: 1, name, age });
        }
    );
 
export type AppType = typeof app;
export default app;

Web Worker with structured cloning

// main.js
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
worker.postMessage({ cmd: "compute", payload: new Float32Array(1_000_000) });
worker.onmessage = (e) => console.log("result:", e.data);
 
// worker.js
self.onmessage = (e) => {
    const { cmd, payload } = e.data;
    if (cmd === "compute") {
        let sum = 0;
        for (let i = 0; i < payload.length; i++) sum += payload[i];
        self.postMessage(sum);
    }
};

End-to-end stack recipes (2026)

A “modern” full-stack JS app in 2026 is rarely just a framework — it’s a curated stack. The combinations that ship:

Next.js 15 + React 19 + Drizzle + Auth.js + Stripe + Vercel — the dominant SaaS recipe.

  • App Router with Server Actions for mutations.
  • Drizzle ORM (TS-first, lighter than Prisma, no shadow DB) talking to Neon / Supabase / PlanetScale.
  • Auth.js v5 (formerly NextAuth) handles OAuth + email + magic links; works in Edge runtime.
  • Stripe webhook handler as a Route Handler with runtime = 'nodejs' for raw-body verification.
  • Deploy: vercel --prod — ~30s build for a medium app, fluid compute pricing.

Astro 5 + Astro DB + Astro Actions + content collections — content-first sites with surgical hydration.

  • getStaticPaths for SSG; prerender = false per-route for SSR.
  • Astro DB (libSQL/Turso under the hood) for typed schema + lightweight ORM in db/config.ts.
  • Actions (actions/index.ts) give server functions callable from any island — typed end-to-end.
  • Server Islands (server:defer) defer heavy components until after first paint.
  • Adapters for Vercel / Netlify / Cloudflare / Node — one codebase, multi-target.

Remix → React Router 7 migration — the merge as of Nov 2024.

  • “Framework mode” = the old Remix (loaders, actions, file routing, full SSR).
  • “Library mode” = classic React Router (client-side declarative routing).
  • Migration: npx codemod remix/2/react-router/upgrade rewrites imports. No data-loader semantic changes.
  • File-based routing under app/routes/; flat-routes convention.
  • Deploy: any Node host, Vercel, Cloudflare Workers, Deno Deploy.

SvelteKit + Svelte 5 (runes) + Lucia v3 + better-sqlite3 — minimal, fast, no-framework-lock.

  • Runes ($state, $derived, $effect) replace store boilerplate; reactive in .svelte.js modules.
  • Lucia v3 (now framework-agnostic) handles sessions; works with adapter-static or adapter-node.
  • better-sqlite3 for local-first apps; LiteFS / Turso for replicated edge SQLite.
  • Deploy: Vercel, Netlify, Cloudflare Pages, Node, or bun.

Bun.serve native HTTP — when you don’t want a framework.

Bun.serve({
    port: 3000,
    routes: {                                 // pattern matching (Bun 1.2+)
        "/api/users/:id": req => Response.json({ id: req.params.id }),
        "/api/health": new Response("OK"),
    },
    fetch(req) {                              // fallback
        return new Response("Not found", { status: 404 });
    },
});

Bun’s HTTP server hits ~700k req/s on a single core — 3-4x Node’s http.createServer baseline, and Bun’s router uses radix tree matching natively.

Performance numbers (cold start + steady state, 2026 measurements)

Cold-start times for “hello world” HTTP servers on warm Linux box (M2 Pro, EC2 c7i.large equivalents):

RuntimeCold startSteady-state req/s (1 core)Notes
V8 (raw, no Node)~50msn/aIsolate creation only
Node 22 LTS~150ms~75k (http.createServer)--watch and --env-file builtin
Node 24 LTS~120ms~80kMaglev enabled, V8 12.4
Deno 2.0~80ms~150k (Deno.serve)Hyper Rust HTTP under the hood
Bun 1.2~30ms~700k (Bun.serve)JSC + uWebSockets-like routing
Cloudflare Workers<1ms~50k per isolateV8 isolates, no Node API
AWS Lambda Node 22~250ms (cold)n/aSnapStart cuts to ~100ms
AWS Lambda + LLRT~50msn/aAmazon’s QuickJS-based runtime; no JIT, smaller surface

Event loop blocking: a single CPU-bound for loop (1M iterations) blocks the entire event loop for ~5-15ms. Offload to worker_threads (Node) or Worker (browser) for anything >10ms. The --inspect-brk profiler shows this as a “long task” red flag.

Common JS-specific gotchas with worked examples

1. === vs == coercion== invokes ToPrimitive and silently coerces:

0 == ""              // true   (both → 0)
0 == "0"             // true
[] == false          // true   (both → 0)
[1] == "1"           // true   ([1].toString() === "1")
null == undefined    // true   (special case)
NaN == NaN           // false  (NaN compares unequal to itself)
{} == "[object Object]"  // false (object on LHS never coerces with this rule)

Lint with eqeqeq (ESLint) or noEqEq (Biome) — disallow == except for == null.

2. Microtask vs macrotask ordering — Promises always drain before the next setTimeout:

console.log("1");
setTimeout(() => console.log("2"), 0);    // macrotask
Promise.resolve().then(() => console.log("3"));   // microtask
queueMicrotask(() => console.log("4"));
console.log("5");
 
// Output: 1, 5, 3, 4, 2
// Sync code first; then microtask queue drained entirely; then next macrotask

A long microtask chain (.then().then().then()) can starve macrotasks indefinitely — UI freezes, timers don’t fire.

3. Prototype pollution — user-controlled keys reaching Object.assign / spread / lodash.set:

// Vulnerable
const opts = JSON.parse(userInput);
const cfg = { ...defaults, ...opts };   // if opts = {"__proto__": {"isAdmin": true}}
//                                          ALL plain objects now have isAdmin = true
 
// Safe: Object.create(null) — no prototype, no __proto__ pollution route
const cfg = Object.assign(Object.create(null), defaults, opts);
 
// Safe: Map for user-keyed data
const cfg = new Map(Object.entries(opts));

CVEs in lodash, jQuery, Mongoose all traced to this pattern. ESLint no-proto + structured cloning + Object.create(null) mitigate.

Citations