Field note / javascript

publishedupdated 2026-05-04#javascript#browser-api#intersection-observer#resize-observer#abort-controller

Browser APIs I Reach For Often

The browser primitives I rely on for real product problems: observers, cancellation, performance signals, cross-tab messaging, and main-thread control.

TL;DR

  • The browser already ships strong primitives for visibility, size changes, cancellation, performance timing, and cross-context communication. I try those before adding abstraction layers.
  • I prefer observers over polling, AbortController over ad hoc boolean flags, and browser-native eventing over overbuilt custom infrastructure.
  • Senior frontend interviews often test whether you can solve runtime problems with platform APIs instead of rebuilding the platform in userland.
  • The most valuable browser API knowledge is practical: cleanup, backpressure, main-thread cost, and choosing the right primitive for the failure mode.

Context

When frontend systems get complex, teams often reach for framework-specific fixes before asking what the browser already gives them.

The recurring problems are familiar:

  • typeahead requests racing each other
  • infinite scroll causing jank
  • dashboard cards resizing unpredictably
  • main-thread work blocking input
  • multiple tabs getting out of sync

Most of those are better solved with platform primitives than with custom event buses and timer-heavy utilities.

The Approach

1. Match the API to the signal you actually need

This is the quick decision table I keep in my head:

ProblemAPI I reach forWhy
visibility / lazy loadIntersectionObserverasync and efficient
element size changesResizeObservercontainer-aware without global resize
DOM mutationsMutationObservertargeted structural observation
canceling requests or listenersAbortControllerone cancellation primitive for many async edges
perf instrumentationPerformanceObserverbrowser-native signals for web vitals and long tasks
cross-tab syncBroadcastChannel or storage eventsame-origin coordination
CPU-heavy workWeb Workerkeeps the main thread responsive

2. Use IntersectionObserver for feed and infinite-scroll workloads

This is still one of the highest-value APIs for product UIs.

const sentinel = document.querySelector("#sentinel")

const observer = new IntersectionObserver(
  async (entries) => {
    for (const entry of entries) {
      if (!entry.isIntersecting) continue
      await loadNextPage()
    }
  },
  {
    rootMargin: "200px 0px",
  }
)

observer.observe(sentinel)

Why I like it:

  • no scroll listener math
  • async visibility signal
  • root margin lets me preload before the user hits the edge

What I still watch:

  • duplicate fetches when the sentinel remains visible
  • lack of virtualization on long feeds
  • forgetting to disconnect the observer on teardown

3. Use AbortController for search, navigation, and cleanup

This is the most practical async primitive frontend teams underuse.

let activeController = null

async function search(query) {
  activeController?.abort()
  activeController = new AbortController()

  try {
    const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: activeController.signal,
    })

    const data = await response.json()
    renderResults(data)
  } catch (error) {
    if (error.name !== "AbortError") {
      reportError(error)
    }
  }
}

It also works nicely for event cleanup:

const controller = new AbortController()

window.addEventListener("resize", handleResize, {
  signal: controller.signal,
})

document.addEventListener("visibilitychange", handleVisibility, {
  signal: controller.signal,
})

return () => controller.abort()

That gives me one teardown point instead of manually removing every listener.

4. Use ResizeObserver when layout depends on container size, not viewport size

Global window.resize listeners are often the wrong abstraction for component systems.

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const width = entry.contentRect.width
    updateLayoutMode(width > 720 ? "wide" : "narrow")
  }
})

observer.observe(document.querySelector("[data-chart-panel]"))

This is especially useful for dashboards, card layouts, and embedded widgets whose size is controlled by the container rather than the viewport.

5. Use PerformanceObserver when diagnosing real UI pain

For senior frontend work, performance knowledge should not stop at console.time.

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === "longtask") {
      console.log("Long task detected:", entry.duration)
    }
  }
})

observer.observe({ entryTypes: ["longtask"] })

This is useful when debugging:

  • hydration cost
  • large JSON parsing
  • chart rendering spikes
  • main-thread stalls during input

6. Use cross-tab APIs when product state should stay coherent

For auth/session changes, draft indicators, or lightweight workspace sync, BroadcastChannel is often enough.

const channel = new BroadcastChannel("workspace")

channel.postMessage({
  type: "filters-updated",
  payload: { status: "open" },
})

channel.onmessage = (event) => {
  syncFilters(event.data.payload)
}

This is much cleaner than abusing polling or server round-trips for same-browser coordination.

7. Think in terms of cleanup and backpressure

The best browser API usage is not just “I know the API exists.” It is:

  • I disconnect observers
  • I abort superseded work
  • I do not flood the main thread
  • I choose streaming or workers when data volume grows

That is the difference between demo knowledge and production knowledge.

Trade-offs

  • Observer APIs are efficient, but they still need cleanup, sensible thresholds, and protection against repeated triggers.
  • MutationObserver is powerful, but it gets expensive fast when teams watch broad subtrees without a narrow purpose.
  • AbortController is the cleanest cancellation model I know on the web platform, but older codebases often need migration work to standardize around it.
  • Browser-native APIs reduce dependency load, but they demand stronger runtime literacy from the team.

References