
Building Polyfills for JavaScript Array and String Methods

Modern JavaScript includes a wide range of array and string methods that simplify coding and improve the readability of our code. However, older browsers or specific environments often lack support for some of these methods. This is where polyfills come in; they allow us to implement equivalent functionality, bridging gaps in browser support.
In this article, I intend to explain the concept of polyfills, explore some commonly missing JavaScript methods, and guide you through building polyfills for them. By the end, you should hopefully feel confident in both understanding and writing polyfills to ensure your code runs seamlessly across environments.
What are Polyfills?
A polyfill is a piece of JavaScript code that replicates the functionality of a newer method or feature, enabling its use in environments that do not support it natively. For example, the Array.prototype.includes method, introduced in ES6, might not be available in older browsers. By writing a polyfill, we can mimic its behaviour so that we can still use it and our code doesn't break when loaded on those older browsers.
Polyfills are distinct from shims. Whilst both fill in missing functionality, a shim modifies or extends native objects, often with custom behaviour. Polyfills strictly replicate the expected functionality to maintain standards.
Creating a Polyfill: The General Approach
When creating a polyfill, there's a fairly simple and structured approach that we can follow:
1. Check for Native Support:
Before defining a polyfill, we need to check if the method already exists within that environment. This prevents unnecessary overwriting of native implementations. Thus, we wrap our polyfill in an if‑not:
if (!Array.prototype.includes) { // Our polyfill goes here}2. Define the Functionality:
We then implement the behaviour as per the official specification, and overriding the (missing) native implementation. It's really important to ensure that the polyfill we write exactly replicates the functionality of the native method.
Array.prototype.includes = function(valueToFind, fromIndex) { // Polyfill logic goes in here};3. Handle Edge Cases:
With our polyfill written, we need to make sure that we also handle special cases such as handling negative indices or optional parameters, to ensure complete compatibility with real‑world usage.
4. Test Extensively:
It goes without saying that the final step is always going to be that it needs to be validated against various test cases to confirm that it behaves identically to the native method. This is also important in areas of the application where these polyfills might get used ‑ manual testing across devices will ‑ hopefully ‑ quickly surface any inconsistencies.
Array Method Polyfills
Starting with array methods, here are some of the most common ones for which I've had to write polyfills in the past.
1. Array.prototype.includes
The includes method determines if an array contains a specific value or not. It returns true if the value is found and false otherwise.
For example:
const numbers = [1, 2, 3];numbers.includes(2); // trueThe Polyfill
if (!Array.prototype.includes) { Array.prototype.includes = function(valueToFind, fromIndex) { // Convert negative index to the correct starting position const start = fromIndex < 0 ? Math.max(this.length + fromIndex, 0) : fromIndex || 0; for (let i = start; i < this.length; i++) { if (this[i] === valueToFind) return true; } return false; };}This works by:
Handle the
fromIndexParameter:- If
fromIndexis a negative number, the code calculates the actual starting index by adding the negative value to the array's length. For example:- If the array has a length of 5 and
fromIndexis ‑2, the starting index becomes5 ‑ 2 = 3.
- If the array has a length of 5 and
- If
fromIndexis positive or undefined, it defaults to0or the provided value.
- If
Iterate Over the Array
:- Using a
forloop, the code begins checking each element in the array starting from the calculated index (start).
- Using a
Check for Equality
:- Inside the loop, each element is compared to
valueToFindusing strict equality (===). This ensures the comparison respects both value and type.- For example,
3 === "3"would returnfalse.
- For example,
- Inside the loop, each element is compared to
Return
trueif a Match is Found:- If the value is found during iteration, the function immediately returns
true.
- If the value is found during iteration, the function immediately returns
Return
falseif No Match is Found:- If the loop completes without finding a match, the function returns
false.
- If the loop completes without finding a match, the function returns
2. Array.prototype.find
The find method returns the first element in an array that satisfies a provided testing function. If no elements match then it returns undefined.
For example:
const numbers = [5, 12, 8];const found = numbers.find(num => num > 10); // 12The Polyfill
if (!Array.prototype.find) { Array.prototype.find = function(callback, thisArg) { for (let i = 0; i < this.length; i++) { if (callback.call(thisArg, this[i], i, this)) { return this[i]; } } return undefined; };}Fortunately, this is a little more straightforward than the last polyfill! This works by:
Iterate Over the Array
:- Using a
forloop, the code applies the callback function to each element in the array.
- Using a
Apply the Callback
:- The
callbackfunction is invoked with the current element, index, and the array itself. If the callback returnstrue, the iteration stops, and the current element is returned.
- The
Handle
thisArgContext:- The optional
thisArgparameter allows the callback to execute in a specific context using.call().
- The optional
Return
undefinedif No Match is Found:- If no elements satisfy the callback, the function returns
undefined.
- If no elements satisfy the callback, the function returns
3. Array.prototype.flat
The flat method creates a new array with all sub‑array elements concatenated into it, up to the specified depth.
For example:
const arr = [1, [2, [3, [4]]]];arr.flat(2); // [1, 2, 3, [4]]The Polyfill
if (!Array.prototype.flat) { Array.prototype.flat = function(depth = 1) { const flatten = (arr, d) => { return d > 0 ? arr.reduce( (acc, val) => acc.concat(Array.isArray(val) ? flatten(val, d - 1) : val), [] ) : arr.slice(); }; return flatten(this, depth); };}This works by:
Default Depth
:- If no depth is specified, it defaults to
1.
- If no depth is specified, it defaults to
Recursive Flattening
:- The
flattenfunction recursively reduces the array, concatenating nested arrays if the depth (d) is greater than0.
- The
Preserve Original
:- When
depthis0, the array is returned as‑is usingslice.
- When
4. Array.prototype.reduceRight
The reduceRight method applies a function against an accumulator and each value of the array (from right‑to‑left) to reduce it to a single value.
For example:
const arr = ['right', 'to', 'reduce'];arr.reduceRight((acc, curr) => `${acc} ${curr}`, ''); // " reduce to right"The Polyfill
if (!Array.prototype.reduceRight) { Array.prototype.reduceRight = function(callback, initialValue) { if (this == null) { throw new TypeError('Array.prototype.reduceRight called on null or undefined'); } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } const array = Object(this); let length = array.length >>> 0; let index = length - 1; let value = initialValue; if (arguments.length < 2) { if (length === 0) { throw new TypeError('Reduce of empty array with no initial value'); } value = array[index--]; } for (; index >= 0; index--) { if (index in array) { value = callback(value, array[index], index, array); } } return value; };}From a code perspective, this is quite possibly one of the more complex ones to write, however, the explanation is actually quite straightforward!
Right‑to‑Left Iteration
:- The loop starts at the last element and moves leftward.
Handle Initial Value
:- If
initialValueis not provided, the last element of the array becomes the initial accumulator.
- If
Edge Cases
:- The polyfill throws appropriate errors for empty arrays without an initial value.
String Method Polyfills
1. String.prototype.startsWith
The startsWith method checks if a string begins with a specific substring.
For example:
const str = "Hello, world!";str.startsWith("Hello"); // trueThe Polyfill
Even without the method, this is a fairly straightforward one to replicate:
if (!String.prototype.startsWith) { String.prototype.startsWith = function(search, position) { position = position || 0; return this.substring(position, position + search.length) === search; };}This works by:
Extract Substring
:- The method uses
substringto extract a portion of the string starting atpositionand ending atposition + search.length.
- The method uses
Compare Substrings
:- It compares the extracted substring with the
searchstring.
- It compares the extracted substring with the
Default ****
position:- If no
positionis specified, it defaults to0.
- If no
2. String.prototype.endsWith
The exact opposite of startsWith, the endsWith method checks if a string ends with a specific substring.
For example:
const str = "Hello, world!";str.endsWith("world!"); // trueThe Polyfill
Again, being fairly basic string manipulation, this is an easy one to replicate:
if (!String.prototype.endsWith) { String.prototype.endsWith = function(search, length) { const str = length !== undefined ? this.substring(0, length) : this; return str.slice(-search.length) === search; };}This works by:
Handle the
lengthParameter:- If
lengthis specified, the string is truncated usingsubstringto the given length.
- If
Extract Substring
:- The method uses
sliceto extract the ending portion of the string with the same length assearch.
- The method uses
Compare Substrings
:- It compares the extracted portion with the
searchstring to determine if they match.
- It compares the extracted portion with the
3. String.prototype.padStart
The padStart method pads the current string with another string until the resulting string reaches the given length.
For example:
'5'.padStart(3, '0'); // "005"The Polyfill
if (!String.prototype.padStart) { String.prototype.padStart = function(targetLength, padString) { targetLength = targetLength >> 0; // Floor the target length padString = String(padString || ' '); if (this.length >= targetLength) { return String(this); } const padLength = targetLength - this.length; return padString.repeat(Math.ceil(padLength / padString.length)).substring(0, padLength) + String(this); };}This works by:
Calculate Pad Length
:- Determines how many padding characters are needed to meet the target length.
Repeat and Trim
:- Uses
repeatto generate a padding string long enough andsubstringto trim it to the exact required length.
- Uses
Preserve Original
:- If the string is already equal to or longer than the target length, it returns the original string.
4. String.prototype.trim
The trim method removes whitespace from both ends of a string.
For example:
' hello world '.trim(); // "hello world"The Polyfill
With regex, this is an incredibly straightforward one to replicate:
if (!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };}This works by:
Regex for Trimming
:- Matches and removes leading (
^\s+) and trailing (\s+$) whitespace.
- Matches and removes leading (
Replace Operation
:- Utilises
replaceto return the string without the matched whitespace.
- Utilises
Writing Better Polyfills: Best Practices
Check for Native Support
: Always ensure the method is not already implemented in the environment before adding a polyfill.Preserve Standards
: Follow the official specification when writing polyfills to avoid unexpected behaviour.Avoid Overwriting
: Only define the polyfill if the method is truly missing.Test Extensively
: Validate your polyfills against edge cases to ensure reliability.
Wrapping up
Polyfills bridge the gap between modern JavaScript features and older environments. By understanding how to write and implement them, you ensure your code remains functional across a variety of platforms. From array methods like includes and find to string methods such as startsWith and endsWith, polyfills allow you to maintain compatibility without sacrificing functionality.
Key Takeaways
- Polyfills replicate modern JavaScript methods in environments where they are unavailable.
- Writing a polyfill involves adhering to official method specifications and preserving native functionality.
- Always validate polyfills for edge cases to ensure robustness.
By mastering polyfills, you enhance your JavaScript projects' compatibility and longevity.
Related Articles

JavaScript Error Handling Patterns. 
Enhancing Web Typography with text‑wrap: balance. Enhancing Web Typography with
text‑wrap: balance
Merging Multiple Objects in JavaScript. Merging Multiple Objects in JavaScript

Optimising gatsby‑image Even Further. Optimising
gatsby‑imageEven Further
The LeetCode Zigzag Conversion Problem in TypeScript. The LeetCode Zigzag Conversion Problem in TypeScript

The Execution Context in JavaScript. The Execution Context in JavaScript

Margin Collapse in CSS. Margin Collapse in CSS

Using data‑* Attributes and dataset in JavaScript. Using
data‑*Attributes anddatasetin JavaScript
Angular Standalone Components: Do We Still Need Modules? Angular Standalone Components: Do We Still Need Modules?

UseReducer in React. useReducerin React
Vue's provide/inject API: When and How to Use It. Vue's
provide/injectAPI: When and How to Use It
Optimising HTML Markup for SEO. Optimising HTML Markup for SEO