Articles

What Is a Polyfill in JavaScript?

A polyfill is code that implements a missing browser feature so older environments behave as if they supported it natively.

Takina Takina · · 4 min read
Laptop screen showing JavaScript code in blue light

A polyfill is a piece of JavaScript that reimplements a feature the browser running it doesn’t natively support, so application code can call that feature as if it were built in. The term comes from Polyfilla, a UK brand of spackling paste used to fill cracks and gaps — a polyfill fills the gap between what a browser’s JavaScript engine actually implements and what the language or platform spec says should exist.

Why gaps exist in the first place

Browsers don’t all ship the same features at the same time. A new array method, a new global object, or a new Web API can land in one engine months or years before it lands in the others — and users on older browser versions, corporate-locked installs, or less common platforms may never update at all. If your code calls Array.prototype.flat() and it runs in an environment that predates that method, the call throws a TypeError, not a graceful fallback.

A polyfill closes that gap by detecting whether the native feature exists and, if it doesn’t, defining it in plain JavaScript with equivalent behavior:

if (!Array.prototype.flat) {
  Array.prototype.flat = function (depth = 1) {
    return depth > 0
      ? this.reduce(
          (acc, val) =>
            acc.concat(Array.isArray(val) ? val.flat(depth - 1) : val),
          []
        )
      : this.slice();
  };
}

After this runs, [1, [2, 3]].flat() works the same whether the engine implemented flat natively or not. Application code never needs to know which case it’s in.

Polyfills vs transpilers vs shims

These three terms get used loosely and interchangeably, but they solve different problems:

PolyfillTranspilerShim
What it changesAdds a missing runtime APIRewrites syntax to an older equivalentAdapts one API’s interface to match another
ExampleArray.prototype.flatArrow functions → function expressionslocalStorage API backed by cookies
RunsAt runtime, in the browserAt build time, before shippingAt runtime
FixesMissing built-insMissing syntax, which can’t be polyfilledInterface mismatches, not necessarily missing features

A transpiler like Babel handles new syntax — optional chaining, async/await, class fields — by rewriting it into older syntax the target engine can parse. That’s a fundamentally different job from a polyfill: syntax can’t be polyfilled, because a polyfill is just a function, and you can’t define new grammar with a function call. A polyfill only works for features expressible as regular objects, functions, or methods — things you can attach to an existing global or prototype.

Feature detection, not browser sniffing

A well-written polyfill checks whether the feature is already present before defining it, as in the flat example above. This is feature detection: ask “does this thing exist” directly, rather than trying to infer capability from a user-agent string. Browser sniffing is fragile — user agents lie, get spoofed, and change format — while feature detection asks the actual question you care about.

This same principle shows up elsewhere on the platform. Progressive enhancement is built on the same idea at a larger scale: ship a baseline experience that works everywhere, then layer on enhancements only where the underlying capability is confirmed to exist, whether that’s a polyfilled method or a modern CSS feature behind an @supports check.

Where polyfills fit in a modern build

Loading every possible polyfill unconditionally is wasteful — most visitors run modern browsers that need none of it, and shipping the code anyway costs bytes and parse time for nothing. Two patterns address this:

  • Differential serving. Serve a modern, polyfill-free bundle to browsers that support current syntax and APIs (often via <script type="module">, which most legacy browsers won’t even parse), and a separate, polyfilled, transpiled bundle to everything else via <script nomodule>.
  • Targeted polyfill services. A service reads the requesting browser’s user agent, computes exactly which features that browser is missing, and returns only those polyfills. This trades a browser-sniffing dependency for meaningfully smaller payloads on modern browsers.

Either approach connects to the same critical rendering path concerns as any other blocking script: a polyfill bundle loaded synchronously in <head> delays everything after it, so it’s worth being deliberate about what actually needs to load before your application code runs.

Do you still need polyfills today

Fewer than a few years ago, but the need hasn’t disappeared. Modern JavaScript engines converge on new features faster than they used to, and Bun, Node, and browser evergreen updates mean the gap between “specified” and “shipped” is smaller. But polyfills are still relevant for:

  • Genuinely new platform APIs in their first year or two, before every target browser has caught up.
  • Environments you don’t fully control — embedded webviews, older enterprise browsers, some smart TVs and kiosks.
  • Server-side or edge runtimes that don’t implement every browser global, where a polyfill lets isomorphic code run unmodified in both places.

Package managers make this easier to manage than hand-writing detection code: install a maintained polyfill package via npm, pnpm, or yarn rather than pasting one in from a blog post, since edge cases in spec compliance are easy to get subtly wrong by hand.

The takeaway

A polyfill fills a runtime gap by reimplementing a missing built-in feature in plain JavaScript, checked with feature detection so it only activates where the native version is actually absent. It’s distinct from a transpiler, which rewrites syntax rather than adding runtime APIs. The practical move today is to avoid shipping polyfills unconditionally — target them at the browsers that actually need them, whether through differential bundles or a polyfill service, so modern visitors aren’t paying a tax for compatibility they don’t require.

Takina Takina · · 4 min read

Object.groupBy() in JavaScript, Explained

Object.groupBy() buckets array items by a key you compute, replacing the reduce() boilerplate developers have written for years. How it works.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

Svelte 5 Runes Explained

Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.

#JavaScript #Frameworks #Web Development