# How Polyfills Work in JavaScript: A Complete Guide from You Don't Know JS

> Discover how polyfills work in JavaScript. Learn to implement fallback solutions for modern code to run on older browsers. Get the complete guide.

- Repository: [Kyle Simpson/You-Dont-Know-JS](https://github.com/getify/You-Dont-Know-JS)
- Tags: deep-dive
- Published: 2026-02-24

---

**A polyfill is a defensive JavaScript snippet that detects missing native APIs and provides fallback implementations, enabling modern code to execute safely on older browsers or runtimes.**

A **polyfill** (also called a shim) adds missing language features to environments that lack native support. According to the getify/You-Dont-Know-JS repository, polyfills follow a specific detection-and-definition pattern that checks for existing functionality before adding compatible alternatives. This technique allows developers to write modern ECMAScript code while maintaining compatibility with legacy browsers and Node.js versions.

## What Is a JavaScript Polyfill?

A polyfill is a piece of code that replicates a newer JavaScript API in older environments where it does not exist natively. Unlike transpilation, which rewrites syntax at build time, polyfills run at runtime to inject missing methods or objects into the global scope or built-in prototypes. As explained in *Getting Started* (Chapter 1), polyfills enable **forward-compatibility** by letting developers use modern features like `Promise.prototype.finally` while still supporting Internet Explorer 11 or older mobile browsers.

## How Polyfills Work: The Detection Pattern

Polyfills follow a three-step defensive pattern to avoid overwriting native implementations and causing conflicts.

### Feature Detection

The code first checks whether the target feature already exists using a conditional statement. In [`get-started/ch1.md`](https://github.com/getify/You-Dont-Know-JS/blob/main/get-started/ch1.md) (lines 557-579), the book demonstrates this with `if (!Promise.prototype.finally)` to determine if the ES2019 method is missing before defining it.

### Safe Definition

If the feature is absent, the polyfill assigns a function or value to the appropriate object. For example, `Promise.prototype.finally = function …` adds the missing method only when the native implementation is unavailable. This guard clause ensures the polyfill becomes a no-op in modern engines while providing the necessary fallback in legacy environments.

### Spec-Compliant Behavior

Production-grade polyfills aim to mimic the official ECMAScript specification as closely as possible. However, as noted in the You Don't Know JS source code, minimal inline examples used for illustration may skip edge cases like proper handling of `Symbol` species or cancellation semantics. For production use, developers should prefer battle-tested libraries from the [es-shims](https://github.com/es-shims) organization over handwritten implementations.

## When to Use Polyfills

Different scenarios require polyfills to bridge capability gaps between development targets.

**Targeting older browsers** – When supporting Internet Explorer 11 or legacy mobile browsers, native APIs like `Array.prototype.flat` or `Object.hasOwn` are often missing entirely. Include a polyfill that guards against existing implementations using `if (!Feature) …` to prevent conflicts.

**Running code in multiple runtimes** – Node.js versions prior to 12, older V8 engines, and embedded browsers expose different global objects (`global`, `window`, `self`). As shown in [`scope-closures/ch4.md`](https://github.com/getify/You-Dont-Know-JS/blob/main/scope-closures/ch4.md) (lines 998-1009), a universal `globalThis` polyfill normalizes these references across environments.

**Using a new language feature in a library** – Library authors cannot control their consumers' runtime versions. When using modern methods like `Object.hasOwn` (ES2022), ship the polyfill with your library or advise consumers to enable Babel's *useBuiltIns* option to automatically inject necessary fallbacks.

**Prototyping or experimentation** – For quick demos or internal tools, write a minimal inline polyfill as illustrated in the book, but replace it with a robust version before deploying to production. This approach avoids adding dependencies for temporary use cases.

## Production-Grade Polyfill Examples from You Don't Know JS

The You Don't Know JS repository provides concrete implementations for several modern features.

### Polyfilling Promise.prototype.finally (ES2019)

In [`get-started/ch1.md`](https://github.com/getify/You-Dont-Know-JS/blob/main/get-started/ch1.md) (lines 557-579), the book provides a spec-compliant fallback for the `finally` method added in ES2019:

```javascript
if (!Promise.prototype.finally) {
  Promise.prototype.finally = function (fn) {
    return this.then(
      value => Promise.resolve(fn()).then(() => value),
      reason => Promise.resolve(fn()).then(() => { throw reason; })
    );
  };
}

```

This implementation ensures that cleanup logic runs regardless of whether the promise resolves or rejects.

### Polyfilling Object.hasOwn (ES2022)

The `Object.hasOwn` static method provides a safer alternative to `Object.prototype.hasOwnProperty.call()`. As demonstrated in [`objects-classes/ch1.md`](https://github.com/getify/You-Dont-Know-JS/blob/main/objects-classes/ch1.md) (lines 628-637), the polyfill checks for existence before defining:

```javascript
if (!Object.hasOwn) {
  Object.hasOwn = function (obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
  };
}

```

This pattern avoids issues with objects that might have overridden the `hasOwnProperty` method on their prototype chain.

### Polyfilling globalThis Across Environments

Different JavaScript environments expose the global object through different identifiers. The robust cross-environment polyfill from [`scope-closures/ch4.md`](https://github.com/getify/You-Dont-Know-JS/blob/main/scope-closures/ch4.md) (lines 998-1009) handles this fragmentation:

```javascript
const theGlobalScopeObject =
  typeof globalThis !== "undefined" ? globalThis :
  typeof global !== "undefined"   ? global :
  typeof window !== "undefined"   ? window :
  typeof self !== "undefined"     ? self :
  (new Function("return this"))();

```

This code progressively falls back through `globalThis`, `global`, `window`, and `self` before using a last-resort `Function` constructor to access the global scope.

## Risks and Best Practices

While polyfills enable modern development patterns, they introduce specific risks that require mitigation.

**Performance overhead** – Feature detection and additional function definitions execute on every page load or module import. Minimize this impact by placing polyfills behind conditional checks and loading them only when necessary.

**Spec divergence** – Handwritten polyfills often miss edge cases present in the official specification. The `Promise.prototype.finally` example in the book handles basic resolution but might not account for all interactions with promise chains or subclassing. Use established polyfill libraries for critical production code.

**Namespace pollution** – Modifying built-in prototypes like `Array.prototype` or `Object.prototype` can conflict with other libraries or future standard additions. Always check `if (!Target.prototype.method)` before assignment, and consider using **shams** (non-mutating alternatives) when modifying globals is unacceptable.

## Summary

- A **polyfill** detects missing APIs and provides fallback implementations only when native support is absent.
- The defensive pattern uses `if (!Feature)` checks followed by safe assignment to built-in objects or prototypes.
- You should polyfill when targeting legacy browsers, supporting multiple runtimes, shipping libraries with modern dependencies, or prototyping new features.
- Production code should prefer battle-tested libraries like es-shims over minimal inline examples.
- Risks include runtime performance costs, specification edge-case mismatches, and potential namespace conflicts with other polyfills.

## Frequently Asked Questions

### What is the difference between a polyfill and a shim?

A **shim** is any code that intercepts API calls and provides a layer of compatibility, often without strictly following the specification. A **polyfill** is a specific type of shim that implements a standardized API exactly as defined by the specification, allowing code written for the native feature to work unchanged. According to the You Don't Know JS source code, the terms are often used interchangeably, but polyfills specifically aim for spec-compliant behavior while shims may provide alternative interfaces.

### Can polyfills impact application performance?

Yes, polyfills introduce small but measurable overhead. The detection logic runs at parse time or module load time, and the fallback implementations may execute slower than native C++ implementations in the JavaScript engine. For example, a polyfilled `Array.prototype.flat` written in JavaScript cannot match the performance of the native V8 optimization. Mitigate this by only loading polyfills for environments that need them and removing them once you drop support for older browsers.

### Should I write my own polyfills or use a library?

For production applications, you should use established polyfill libraries such as `core-js` or the es-shims collection rather than writing your own. The examples in getify/You-Dont-Know-JS are educational illustrations that demonstrate the detection pattern, but they may lack edge-case handling, proper `Symbol` support, or security considerations like CSP (Content Security Policy) compliance. Use these examples to understand the mechanism, but deploy battle-tested dependencies for production code.

### When can I safely remove polyfills from my codebase?

You can remove polyfills once your application no longer runs in environments that lack the native feature. Conduct analytics on your user base to determine when browsers like Internet Explorer 11 or Node.js versions below 14 fall below your support threshold. After removing polyfills, implement **feature detection** in your test suite to ensure the native implementations behave as expected, as subtle differences between polyfill behavior and native implementations can cause regressions in edge cases.