Polyfill or Ponyfill?

What's the difference between a polyfill and a ponyfill?

  • Published:

In the ever-evolving tech world, 'polyfill' and 'ponyfill' are more than just quirky terms. They're solutions to a common challenge: browser incompatibility with modern web features. But how do they differ? Let's dive in.

If there is one thing we love in the tech space, it’s giving things weird names. Chances are you’ve heard the term “polyfill” or “pollyfill” thrown around. But what is it? And what the heck is a “ponyfill”?

The problem

Both polyfills and ponyfills aim to solve the problem of browsers not supporting certain APIs or features.

Since the dawn of time, web developers have struggled to balance their desire to use new and shiny technologies with the constraints of users’ horrendously outdated browsers.

Luckily, nowadays, most browsers are evergreen, updating automatically and often. Being in this position is less likely than during the browser wars .

But, as I recently found out, the issue is still prevalent. We work in the web-based video-engineering space with TVs, set-top boxes, etc. The update cadence on these devices is slim to non-existent. How often do people update their TVs?

Well, I’m not being entirely fair. Most TVs and set-top boxes rely on a solid, albeit old, Chromium foundation (around version 34). We’re not in the dark times of Internet Explorer 6 any more. However, a challenge persists: what happens when we write code for modern browsers and encounter something ancient?


Let’s look at an example, String.prototype.includes(). It performs a case-sensitive search to determine whether a given string is found within the source string, returning true or false as appropriate. We can see from MDN that it’s supported as far back as Chrome 41 , so it is very much a safe bet to use for modern development, but not so much for an older browser. How do we get this functionality on an older device?

One way is to write for your oldest device - use String.prototype.indexOf() instead, supported as far back as Chrome version 1. However, the signature is not quite the same. indexOf() returns an integer, -1 if not found or the index of the first occurrence if it does. To replicate the boolean return from includes(), we can execute myString.indexOf(search) !== -1. If it is -1, we get false; anything else gives us true. It works, but the very reason includes() exists is to provide a terse, clean, optimised API for a common pattern. We don’t want to see a load of === -1 over the codebase; it would be great to use includes().

Polyfill

As far as I am aware, the term “polyfill” was coined by Remy Sharp. I believe I first heard it at one of his talks in 2010.

The idea is that if you’re trying to use a native API/feature that doesn’t exist or perhaps isn’t fully implemented against the spec in your target browser, you can monkey-patch a workaround, allowing you to use the API feature as intended.

For example, this is a polyfill for includes() that uses indexOf(). The polyfill is only applied if no native API is defined. Perfect!

if (!String.prototype.includes) {
	String.prototype.includes = function (this: string, search: string, start: number = 0): boolean {
		return this.indexOf(search, start) !== -1;
	};
}

Well, no, not quite. The trouble is this can have several adverse side effects.

Don’t modify stuff that you don’t own

  1. Often pollutes and patches the global namespace/built-ins
  2. May not be fully spec compliant - if spec changes and polyfill does not, we’ll potentially have variable output
  3. Very hard to debug - Is it a native issue? Is it the patch?
  4. Breaks the rule - “Don’t modify stuff that you don’t own”

Smoosh-gate

There was an incident lovingly called “Smoosh-gate” that happened back in 2018. Long story short, there was once a popular JavaScript library called MooTools (much like JQuery), and it add a new flatten method to Array.prototype, all was well. Later, this method was put forward as an official update to the Ecma spec. Firefox Nightly subsequently shipped the new native array flatten method (which, being native, was defined on the prototype), breaking older websites running MooTools due to API differences. An engineer joked about renaming the method to smoosh, and things escalated.

So, don’t modify the prototype (and don’t joke about method names).

Ponyfill

Ponyfills often have the same internals as polyfills but do not modify the prototype, mitigating the side effects present in polyfills.

Here is a ponyfilled version of String.prototype.includes():

const stringIncludes = (source: string, search: string, start: number = 0): string => {
	return source.indexOf(search, start) !== -1;
};

As you can see, it’s just an abstraction.

The main downside is that the engineer needs to be aware of the ponyfills available. Unlike polyfills, you can’t just blissfully use modern APIs.

At DAZN in Player Engineering, we’ve moved away from polyfills, using ponyfills from a shared utility library instead. We mitigate the engineer awareness issue with a custom ESlint plugin. The plugin that will flag when a ‘new’ API is used and suggests the ponyfill instead.


Edit: In response to my post on LinkedIn, George Wright made a great point - With polyfilling libraries there is a danger you can bloat your bundle size if you’re not careful. Libraries like CoreJS allow targeted polyfilling and there are tools, like Babel, that can automatically polyfill based on usage in your source. But, this is not foolproof, you are still reliant on the library matching the specification and it introduces an overhead of needing to ensure the library is updated to get the latest patches and fixes.

In fact, it reminded me of an issue we faced at DAZN where adding a polyfill fixed a compatibility issue on Tizen TV but subsequently broke a Movistar set-top box.


In conclusion, while polyfills and ponyfills address browser compatibility issues, ponyfills are often a safer, more explicit approach with fewer risks of hidden failures. Choosing the right approach depends on your specific development context and requirements.