Polyfill: What It Is, How It Works, and Libraries for Emulating APIs

Author: IT Sectr Published: 2026-05-19 Reading time: 9 min

Polyfill is code that emulates missing functionality (APIs, methods, objects) in environments where it is not natively implemented. Polyfill allows you to use modern JavaScript, CSS, or Web API features in older browsers and runtimes. According to MDN Web Docs, polyfills are a key tool for progressive enhancement and ensuring cross-browser compatibility.

Key Takeaways

  • Polyfill is a software emulation of a missing API in a runtime environment where that API is not implemented
  • core-js is the standard polyfill library for modern JavaScript with support for all stage-4 proposals
  • Polyfill.io is a service that dynamically serves polyfills only for the user's browser
  • Transpilation vs polyfill: transpilation converts syntax (arrow function → function), polyfill adds new methods (Array.includes, Promise)
  • Feature detection checks for native implementation before loading a polyfill to avoid conflicts

What Is Polyfill?

Polyfill is a piece of code (usually JavaScript) that implements functionality that the runtime environment does not support natively. The term was coined by Remy Sharp in 2009 as a play on words: Polyfill is analogous to Polyfilla, a filler that fills cracks in a wall. Polyfill fills the gaps between the standard and its support in a specific browser or runtime.

Polyfill does not modify existing code — it extends the runtime environment. If a browser does not support Array.prototype.includes, the polyfill adds this method to the Array prototype before the main code executes. Polyfills can emulate new global objects (Promise, Map, Set, Symbol), static methods (Array.from, Object.assign), and prototype methods.

Feature detection is a mandatory mechanism before installing a polyfill. Instead of checking the user-agent (which browser), you should check for the method's existence: if (!Array.prototype.includes) { Array.prototype.includes = ... }. This ensures that the polyfill does not overwrite the native implementation if it already exists. Google Analytics and other services collect API support data for analysis.

When Polyfills Emerged

The first polyfills appeared in the Internet Explorer 6–8 era (2005–2009), when developers discovered a gap between W3C standards and browser implementations. The term was introduced by Remy Sharp in 2009 at BarCamp London. The first mass polyfill was html5shiv (2009) — a library that adds support for HTML5 tags (<section>, <article>, <nav>) in Internet Explorer.

With the advent of ES6 (2015) and the annual ECMAScript release cycle, the number of required polyfills grew. Each year the standard adds new methods (Array.includes, String.padStart, Object.fromEntries, Promise.allSettled) that are not supported by older browsers. core-js, started as es6-shim in 2014, became a universal solution. As of 2026, core-js contains over 5,000 polyfill modules for ES5–ES2025.

What Can and Cannot Be Polyfilled

CategoryCan Be PolyfilledCannot Be Polyfilled
Prototype methodsArray.includes, String.startsWith
Global objectsPromise, Map, Set, Symbol
Static methodsObject.assign, Array.from
Language syntaxArrow functions, async/await, class
Web APIfetch, IntersectionObserverService Worker (requires native support)

Polyfill vs Transpilation: Differences and Interaction

Transpilation converts new syntax into old syntax (const → var, () => {} → function() {}). Polyfill adds missing methods and objects (Promise, Array.includes). These two mechanisms complement each other: transpilation makes the code syntactically compatible, polyfills ensure API completeness. Babel + core-js is the standard combination for full support.

Babel @babel/preset-env with the useBuiltIns option determines which polyfills are needed based on target browsers. useBuiltIns: "usage" analyzes which APIs are used in the code and imports only the necessary polyfills from core-js. useBuiltIns: "entry" imports all polyfills for target browsers through a single core-js/stable import.

Example: Polyfill for Array.prototype.includes

js
// Checking for existence and adding a polyfill
if (typeof Array.prototype.includes !== "function") {
  Object.defineProperty(Array.prototype, "includes", {
    value: function(searchElement, fromIndex) {
      if (this == null) {
        throw new TypeError("Array.prototype.includes called on null or undefined");
      }
      var arr = Object(this);
      var len = arr.length >>> 0;
      if (len === 0) { return false; }
      var start = fromIndex | 0;
      var k = Math.max(start >= 0 ? start : len + start, 0);

      while (k < len) {
        if (arr[k] === searchElement) { return true; }
        k++;
      }
      return false;
    },
    writable: true,
    configurable: true,
  });
}

// Usage — now safe in any browser
const arr = [1, 2, 3, 4, 5];
console.log(arr.includes(3)); // true

Polyfill for Array.prototype.includes checks whether the method is defined on the Array prototype. If not, it creates the property via Object.defineProperty with flags writable: true, configurable: true. The implementation follows the ES2016 specification: null/undefined check, conversion to object, handling of negative fromIndex. After adding the polyfill, calling arr.includes(3) works in all browsers, including Internet Explorer 11.

core-js: The Standard Polyfill Library

core-js is the most comprehensive JavaScript polyfill library, supporting all TC39 stage-4 proposals (ECMAScript standard). core-js includes polyfills for Promise, Symbol, Map, Set, WeakMap, WeakSet, Array methods, String methods, Object methods, Number methods, Math methods, Reflect, globalThis, and all stage-4 proposals. The current version core-js 3.38+ covers ES5–ES2025.

core-js integrates with Babel through @babel/preset-env and the useBuiltIns option. Without this integration, developers would have to manually import each polyfill: import "core-js/stable/array/includes". @babel/preset-env automatically adds the necessary imports based on target browsers from .browserslistrc. This reduces bundle size — only the needed polyfills are included.

Example: Polyfill for fetch

Fetch API is one of the most commonly polyfilled Web APIs. Native fetch implementation is available in Chrome 42+ (2015), Safari 10.1+ (2017), Firefox 39+ (2015), but is absent in Internet Explorer and older WebViews. The whatwg-fetch polyfill emulates fetch via XMLHttpRequest. An alternative is to use isomorphic-fetch (a polyfill for Node.js and browser) or the universal axios library, which does not require polyfills.

js
// Loading fetch polyfill only for old browsers
if (typeof self.fetch !== "function") {
  import("whatwg-fetch").then(module => {
    self.fetch = module.fetch;
    console.log("fetch polyfill loaded");
  });
}

// Using fetch (works with both polyfill and native API)
async function loadData() {
  try {
    const response = await fetch("https://api.example.com/data");
    const json = await response.json();
    return json;
  } catch (error) {
    console.error("Failed to load:", error);
  }
}

Dynamic import of the fetch polyfill via import() ensures that modern browsers do not load unnecessary code. The polyfill loads asynchronously and does not block the main thread. After loading, self.fetch replaces the native implementation or adds the missing one. This is a progressive enhancement technique: modern browsers get only native code, older ones get the additional polyfill.

Integrating core-js with Babel

js
// babel.config.js — core-js + preset-env
module.exports = {
  presets: [
    ["@babel/preset-env", {
      useBuiltIns: "usage",
      corejs: {
        version: "3.38",
        proposals: true,
      },
      targets: {
        browsers: ["> 0.5%", "not dead", "not op_mini all"],
      },
    }],
  ],
};
none
# .browserslistrc — target browsers
> 0.5%
last 2 versions
not dead
not op_mini all
ie >= 11
not ios_saf < 12

useBuiltIns: "usage" analyzes the code and adds only the polyfills that are actually used. corejs.version specifies the core-js version in the project. targets.browsers defines the minimum browser level — the older the target browsers, the more polyfills will be included. .browserslistrc is used not only by Babel but also by Autoprefixer, PostCSS, and Stylelint for consistent targeting.

Polyfill.io and Dynamic Polyfill Loading

Polyfill.io is a service (and an eponymous library) that dynamically determines which polyfills the user's browser needs and returns only those. Polyfill.io uses the User-Agent header to determine the browser version and serves a minimal set of polyfills. This reduces the amount of data transferred compared to a universal polyfill bundle.

Connecting Polyfill.io is done via a <script> tag before the main application code. The service analyzes the User-Agent and returns a JavaScript file with polyfills only for that browser. Chrome will receive no polyfills, IE 11 will receive the full set. This is an optimal approach for performance: modern browsers do not load unnecessary code.

Connecting Polyfill.io

html
<!-- Polyfill.io: dynamic loading -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=Promise%2CArray.prototype.includes%2CObject.assign%2Cfetch"></script>

<!-- Local Polyfill.io version -->
<script src="/js/polyfill.js"></script>
<script>
  // feature detection for fetch
  if (!self.fetch) {
    loadScript("/js/fetch-polyfill.js");
  }
</script>

The features parameter in the Polyfill.io URL specifies which polyfills to load. Possible values: method names (Array.prototype.includes), global objects (Promise), or flags (es6, es2016). The "default" flag loads a basic set for modern JavaScript. For production projects, it is recommended to host Polyfill.io on your own CDN or use a local version of the library for availability control.

Polyfills in Mobile Applications and WebView

WebView in mobile applications (Android WebView, WKWebView on iOS) is a special environment for polyfills. The WebView version depends on the OS version and the installed Chrome System WebView update (Android) or WKWebView from iOS Safari. In older versions of Android (4.4, 5.0), WebView is based on Chromium 30–37 — without support for fetch, Promise, IntersectionObserver.

React Native uses JavaScriptCore (iOS) or Hermes (Android) — these engines implement ES6+ differently. JavaScriptCore on iOS supports most ES6 features but may lack some stage-3 proposals. Hermes (used by default in React Native 0.70+) supports a limited set of the ES standard — polyfills are mandatory for it.

Checking WebView Support

js
// feature detection for WebView
const polyfills = [];

// Promise
if (typeof Promise === "undefined") {
  polyfills.push("Promise");
}

// Fetch API
if (typeof self.fetch === "undefined") {
  polyfills.push("fetch");
}

// IntersectionObserver (needed for lazy loading)
if (typeof IntersectionObserver === "undefined") {
  polyfills.push("IntersectionObserver");
}

// Dynamic polyfill loading
if (polyfills.length > 0) {
  const script = document.createElement("script");
  script.src = "https://cdn.polyfill.io/v3/polyfill.min.js"
    + "?features=" + polyfills.join(",");
  document.head.appendChild(script);
}

Feature detection for WebView checks for the presence of critical APIs (Promise, fetch, IntersectionObserver) and dynamically loads polyfills only for missing ones. This ensures that modern WebViews (Chrome 100+ on Android 12) do not load unnecessary code, while older WebViews (Android 5.0) get the required support.

Frequently Asked Questions

Are polyfills needed for React Native?

React Native on Hermes requires polyfills for some ES methods: Array.flat, Array.flatMap, globalThis, TextEncoder. It is recommended to include core-js or react-native-polyfill-globals for production builds. JavaScriptCore on iOS supports more features, but may also require polyfills for stage-3 proposals.

Do polyfills affect performance?

Polyfills reduce performance by 1–5% since JavaScript implementation is slower than native C++ implementation in the engine. For example, a Promise polyfill in pure JS is slower than a native Promise in V8. However, for most applications the difference is negligible. For critical code, it is recommended to check for native implementation via feature detection.

How is a polyfill different from transpilation?

Transpilation converts syntax: const → var, arrow functions → function. Polyfill adds new objects/methods: Promise, Array.includes, fetch. Transpilation works at build time, polyfill loads at runtime. Both mechanisms are necessary for full support of modern code in older environments.

Can polyfills be avoided in 2026?

Yes, if your target audience uses only modern browsers (Chrome 90+, Safari 15+, Firefox 90+). For projects supporting older devices or enterprise users (Internet Explorer 11 is still used in the government sector), polyfills are mandatory. Analyze your audience's browser statistics via Google Analytics.

How large is the core-js polyfill bundle?

core-js in a full build weighs ~85 KB (gzip). When using useBuiltIns: "usage" in Babel, only the needed polyfills are included, reducing the size to 5–30 KB depending on target browsers. For modern browsers (Chrome 100+), no polyfills may be required at all.

Summary

  • Polyfill is an emulation of a missing API in a runtime environment, ensuring compatibility of modern code with older environments
  • core-js is the standard polyfill library for ES5–ES2025, integrated with Babel via @babel/preset-env
  • Polyfill.io is a service for dynamic polyfill loading based on the browser's User-Agent
  • Transpilation + polyfills is a comprehensive solution: Babel transforms syntax, core-js adds missing APIs
  • Feature detection checks for native implementation before loading a polyfill for performance
  • WebView and Hermes require mandatory polyfills for fetch, Promise, and IntersectionObserver in older versions

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also