Core Concepts

2 min read
Rapid overview

JavaScript Core Concepts

Modern JavaScript for frontend work relies on understanding the runtime, language mechanics, and browser APIs. This guide focuses on concepts you will apply before adding frameworks.

Execution model

  • Call stack + heap: Functions execute on the call stack; objects/closures live on the heap. Avoid unbounded recursion and large synchronous work that blocks rendering.
  • Event loop: Macrotasks (timers, I/O) and microtasks (promises) interleave. Prefer microtasks for post-render follow-ups, and batch DOM reads/writes to avoid layout thrash.
  • Concurrency model: Single-threaded with cooperative multitasking. Use Web Workers for CPU-bound tasks; keep UI thread free.

Values, types, and coercion

  • Primitives vs objects: string, number, boolean, bigint, symbol, undefined, null vs object/function wrappers.
  • Equality: Prefer ===; know how == coerces. Avoid implicit coercion in critical paths.
  • Immutability: Favor pure functions and immutable updates (e.g., spread, Array.prototype.map) to simplify reasoning and compatibility with React/Angular change detection.

Functions, scope, and closure

  • Lexical scope: Variables resolve from the declaration environment (let/const block scope, var function scope).
  • Closures: Functions capture surrounding bindings; key for callbacks, event handlers, and module patterns.
  • this binding: Depends on call-site. Use arrow functions to avoid rebinding, or explicit call/apply/bind when needed.

Objects, prototypes, and classes

  • Prototype chain: Property lookup walks prototypes. Use Object.create(null) for pure dictionaries to avoid inherited keys.
  • Classes: Sugar over prototypes. Keep constructors side-effect free; use private fields for encapsulation when supported.
  • Composition over inheritance: Prefer small utilities and factory functions to deep class hierarchies.

Modules and packaging

  • ESM first: Use import/export with bundlers (Vite, Webpack, esbuild). Avoid mixing ESM and CommonJS in the same file.
  • Tree shaking: Export named bindings; avoid dynamic requires to enable dead code elimination.
  • Environment boundaries: Browser vs Node — know what needs polyfills and how to lazy-load optional features.

Async and networking

  • Promises and async/await: Always return promises; wrap parallel work with Promise.all and handle rejection paths.
  • Fetch patterns: Centralize fetch helpers for auth/errors/retries; treat response.ok as the gate for success.
  • Cancellation: Use AbortController for fetch and long-running tasks.

DOM, events, and performance

  • DOM access: Prefer query selectors scoped to containers; avoid forced synchronous layout (offsetWidth after mutations) without batching.
  • Events: Delegate where possible; respect accessibility semantics (keyboard + pointer).
  • Performance: Measure with Performance APIs; defer non-critical work with requestIdleCallback/setTimeout.

Error handling and resiliency

  • Errors vs rejections: Normalize error handling paths; attach context and user-friendly messages.
  • Boundary patterns: Use guard clauses and feature detection; fail fast on unsupported APIs.

Testing and quality

  • Linting/formatting: ESLint + Prettier baselines; enforce no-floating-promises, eqeqeq, and import hygiene.
  • Unit/integration tests: Favor fast, deterministic tests; mock boundaries (network, time) and assert user-visible outcomes.

Interview-ready snippets

  • Debounce vs throttle implementations and when to prefer each.
  • Promise utilities: timeout wrapper, retry with backoff, and pooled concurrency executor.
  • Event loop tracing: predict log order for setTimeout, Promise.resolve, and synchronous logs.