Essential Polyfill Writing Patterns

1. Polyfill Structure and Template Patterns

Component Pattern Purpose Example
Feature Detection if (!Type.prototype.method) Check if polyfill needed Prevent overwriting native implementation
IIFE Wrapper (function() { ... })() Isolate scope and prevent pollution Avoid variable leakage to global scope
Strict Mode 'use strict'; Enforce better coding practices Catch common errors early
Type Checking if (this == null) throw TypeError Validate context and arguments Match native behavior for errors
ToObject Coercion var O = Object(this) Convert this to object safely Handle primitives correctly
Length Coercion len = O.length >>> 0 Convert length to uint32 Match array length behavior

Example: Complete polyfill template structure

// Standard Polyfill Template
(function() {
    'use strict';
    
    // Feature detection - exit if already exists
    if (Array.prototype.includes) {
        return;
    }
    
    // Polyfill implementation
    Array.prototype.includes = function(searchElement, fromIndex) {
        // Step 1: Type check - throw if this is null/undefined
        if (this == null) {
            throw new TypeError('Array.prototype.includes called on null or undefined');
        }
        
        // Step 2: Convert this to object
        var O = Object(this);
        
        // Step 3: Get and convert length to unsigned 32-bit integer
        var len = parseInt(O.length) || 0;
        
        // Step 4: Handle edge cases
        if (len === 0) {
            return false;
        }
        
        // Step 5: Process fromIndex parameter
        var n = parseInt(fromIndex) || 0;
        var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
        
        // Step 6: Main algorithm
        while (k < len) {
            var currentElement = O[k];
            // Handle NaN comparison (NaN === NaN should be true)
            if (searchElement === currentElement ||
                (searchElement !== searchElement && currentElement !== currentElement)) {
                return true;
            }
            k++;
        }
        
        // Step 7: Return result
        return false;
    };
})();
Note: Always wrap polyfills in an IIFE to avoid polluting global scope. Use strict mode to catch errors and ensure feature detection runs first to avoid overwriting native implementations.

2. Prototype Extension Best Practices

Practice Do This Don't Do This Reason
Check Existence if (!proto.method) Unconditional assignment Preserve native implementation
Use defineProperty Object.defineProperty() proto.method = fn Control enumerability and writability
Non-enumerable enumerable: false enumerable: true Don't break for...in loops
Configurable configurable: true configurable: false Allow future modifications
Test Context if (this == null) throw Assume this is valid Match native error behavior
Handle Edge Cases Check length, NaN, undefined Assume happy path Spec compliance and robustness

Example: Proper prototype extension with defineProperty

// Good: Using Object.defineProperty
if (!Array.prototype.find) {
    Object.defineProperty(Array.prototype, 'find', {
        value: function(predicate) {
            if (this == null) {
                throw new TypeError('this is null or not defined');
            }
            if (typeof predicate !== 'function') {
                throw new TypeError('predicate must be a function');
            }
            
            var O = Object(this);
            var len = O.length >>> 0;
            var thisArg = arguments[1];
            
            for (var k = 0; k < len; k++) {
                if (k in O) {
                    var kValue = O[k];
                    if (predicate.call(thisArg, kValue, k, O)) {
                        return kValue;
                    }
                }
            }
            return undefined;
        },
        configurable: true,
        writable: true,
        enumerable: false  // Critical: prevents for-in enumeration
    });
}

// Bad: Direct assignment (enumerable by default)
// Array.prototype.find = function() { ... };  // DON'T DO THIS
Warning: Never extend prototypes of native objects without feature detection. Always use Object.defineProperty with enumerable: false to avoid breaking existing code.

3. Static Method Polyfill Implementation

Type Pattern Check Method Example
Object Static Object.assign = function() {} if (!Object.assign) Object.keys, Object.values, Object.entries
Array Static Array.from = function() {} if (!Array.from) Array.from, Array.of, Array.isArray
Number Static Number.isNaN = function() {} if (!Number.isNaN) Number.isFinite, Number.parseInt
String Static String.fromCodePoint = function() {} if (!String.fromCodePoint) String.raw, String.fromCodePoint
Math Static Math.trunc = function() {} if (!Math.trunc) Math.sign, Math.trunc, Math.cbrt

Example: Static method polyfills

// Object.assign polyfill
if (!Object.assign) {
    Object.assign = function(target) {
        'use strict';
        if (target == null) {
            throw new TypeError('Cannot convert undefined or null to object');
        }
        
        var to = Object(target);
        
        for (var index = 1; index < arguments.length; index++) {
            var nextSource = arguments[index];
            
            if (nextSource != null) {
                for (var nextKey in nextSource) {
                    if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                        to[nextKey] = nextSource[nextKey];
                    }
                }
            }
        }
        return to;
    };
}

// Array.from polyfill
if (!Array.from) {
    Array.from = function(arrayLike, mapFn, thisArg) {
        var C = this;
        var items = Object(arrayLike);
        var len = items.length >>> 0;
        
        var A = typeof C === 'function' ? 
                Object(new C(len)) : new Array(len);
        
        var k = 0;
        while (k < len) {
            var kValue = items[k];
            if (mapFn) {
                A[k] = typeof thisArg === 'undefined' ? 
                       mapFn(kValue, k) : 
                       mapFn.call(thisArg, kValue, k);
            } else {
                A[k] = kValue;
            }
            k++;
        }
        A.length = len;
        return A;
    };
}

// Number.isNaN polyfill (more accurate than global isNaN)
if (!Number.isNaN) {
    Number.isNaN = function(value) {
        // NaN is the only value that is not equal to itself
        return value !== value;
    };
}

4. Object.defineProperty Advanced Usage

Descriptor Value Effect Use Case
value Any value The actual function or value Set the method implementation
writable true/false Can property be changed with assignment Usually true for polyfills
enumerable false Appears in for...in loops Must be false to match native behavior
configurable true Can descriptor be changed/deleted Allow future updates
get Function Getter function for property Computed properties
set Function Setter function for property Validation on assignment

Example: Advanced defineProperty usage patterns

// Data descriptor (value + writable)
Object.defineProperty(Array.prototype, 'last', {
    get: function() {
        return this[this.length - 1];
    },
    set: function(value) {
        this[this.length - 1] = value;
    },
    enumerable: false,
    configurable: true
});

// Usage: arr.last returns last element
var arr = [1, 2, 3];
console.log(arr.last);  // 3
arr.last = 5;
console.log(arr.last);  // 5

// Batch property definition
if (!Object.defineProperties) {
    Object.defineProperties = function(obj, properties) {
        for (var prop in properties) {
            if (properties.hasOwnProperty(prop)) {
                Object.defineProperty(obj, prop, properties[prop]);
            }
        }
        return obj;
    };
}

// Define multiple properties at once
Object.defineProperties(String.prototype, {
    "trim": {
        value: function() {
            return this.replace(/^\s+|\s+$/g, "");
        },
        writable: true,
        enumerable: false,
        configurable: true
    },
    "trimStart": {
        value: function() {
            return this.replace(/^\s+/, "");
        },
        writable: true,
        enumerable: false,
        configurable: true
    }
});
Note: Use accessor descriptors (get/set) for computed properties. Use data descriptors (value/writable) for methods. Never mix both in the same descriptor.

5. UMD and ES Module Polyfill Patterns

Format Detection Export Pattern Use Case
CommonJS typeof module !== 'undefined' module.exports = polyfill Node.js environments
AMD typeof define === 'function' && define.amd define([], factory) RequireJS, legacy browsers
ES Module Separate .mjs file export default polyfill Modern bundlers, native ESM
Global Fallback for all window.polyfill = ... Browser script tags
UMD Combines all patterns Check order: AMD, CommonJS, Global Universal compatibility

Example: UMD wrapper pattern

// Universal Module Definition (UMD) Pattern
(function(root, factory) {
    'use strict';
    
    // AMD (RequireJS)
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    }
    // CommonJS (Node.js)
    else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    }
    // Browser globals
    else {
        root.myPolyfill = factory();
    }
}(typeof self !== 'undefined' ? self : this, function() {
    'use strict';
    
    // Feature detection
    if ('myFeature' in window) {
        return window.myFeature;
    }
    
    // Polyfill implementation
    function MyPolyfill() {
        // Implementation here
    }
    
    MyPolyfill.prototype.method = function() {
        // Method implementation
    };
    
    // Install globally if needed
    if (typeof window !== 'undefined') {
        window.myFeature = MyPolyfill;
    }
    
    // Return for module systems
    return MyPolyfill;
}));

// ES Module format (separate file)
// polyfill.mjs
export default function polyfill() {
    if (!Array.prototype.at) {
        Array.prototype.at = function(index) {
            var len = this.length;
            var k = index >= 0 ? index : len + index;
            return (k >= 0 && k < len) ? this[k] : undefined;
        };
    }
}

// Usage in ES modules
// import polyfill from './polyfill.mjs';
// polyfill();
Note: For maximum compatibility, use UMD pattern in polyfills. Modern projects can use ES modules (.mjs) with build tool transpilation for legacy support.

6. Idempotency and Safe Execution Patterns

Pattern Implementation Purpose Benefit
Existence Check if (!Type.prototype.method) Only add if missing Prevent double-loading issues
Early Return if (hasFeature) return; Exit immediately if native exists Zero overhead for modern browsers
Version Check if (polyfill.version) return; Track polyfill installation Avoid conflicts with other polyfills
Strict Equality feature === undefined Precise undefined checking Distinguish from null or falsy values
IIFE Isolation (function(){ ... })() Create private scope Prevent variable name collisions
Namespace Guard window.POLYFILLS = {} Track loaded polyfills Coordinate multiple polyfill scripts

Example: Idempotent polyfill patterns

// Pattern 1: Simple existence check
(function() {
    'use strict';
    
    // Early return if already exists
    if (Array.prototype.includes) {
        return;
    }
    
    // Polyfill implementation
    Array.prototype.includes = function(search) {
        // Implementation...
    };
})();

// Pattern 2: Version tracking
(function() {
    'use strict';
    
    // Check if already loaded
    if (window.__POLYFILLS__ && window.__POLYFILLS__.arrayIncludes) {
        return;
    }
    
    // Initialize polyfill registry
    window.__POLYFILLS__ = window.__POLYFILLS__ || {};
    
    if (!Array.prototype.includes) {
        Array.prototype.includes = function(search) {
            // Implementation...
        };
    }
    
    // Mark as loaded
    window.__POLYFILLS__.arrayIncludes = '1.0.0';
})();

// Pattern 3: Safe multiple execution
(function(global) {
    'use strict';
    
    // Create polyfill only once
    function createPolyfill() {
        // Check each feature individually
        if (!Array.prototype.find) {
            Object.defineProperty(Array.prototype, 'find', {
                value: function(predicate) { /* ... */ },
                configurable: true,
                writable: true,
                enumerable: false
            });
        }
        
        if (!Array.prototype.findIndex) {
            Object.defineProperty(Array.prototype, 'findIndex', {
                value: function(predicate) { /* ... */ },
                configurable: true,
                writable: true,
                enumerable: false
            });
        }
    }
    
    // Safe to call multiple times - only executes missing polyfills
    createPolyfill();
    
})(typeof window !== 'undefined' ? window : global);

Key Takeaways - Polyfill Writing

  • Always wrap polyfills in IIFE with strict mode
  • Use feature detection before adding any polyfill
  • Set enumerable: false when extending prototypes
  • Match native error behavior with proper type checking
  • Use UMD pattern for maximum compatibility
  • Make polyfills idempotent - safe to load multiple times