Vivek
Back to Articles

JavaScript Polyfills (Interview Edition)

9/28/2025


JavaScript Polyfills

This guide gives you carefully picked polyfills, each framed as a small problem you might be asked in interviews, followed by a clean solution and key caveats.

What’s a polyfill? A snippet that implements a modern API in older environments that don’t support it.


✅ How to Use This

  • Each item includes: Problem → Polyfill → Notes
  • All polyfills guard against re-defining if a native implementation already exists.
  • Aim for clarity over hyper-optimized edge-case handling (unless noted).

1) Function.prototype.bind

Problem: Implement bind that sets this and supports partial args.

if (!Function.prototype.bind) {
  Function.prototype.bind = function (context, ...boundArgs) {
    const target = this;
    if (typeof target !== 'function') throw new TypeError('Bind target must be callable');
    function boundFn(...callArgs) {
      // If used with new, ignore provided context and bind to the new instance
      const isNew = this instanceof boundFn;
      return target.apply(isNew ? this : context, boundArgs.concat(callArgs));
    }
    // Maintain prototype for new-bound functions
    if (target.prototype) {
      boundFn.prototype = Object.create(target.prototype);
    }
    return boundFn;
  };
}

Notes: Handles constructor behavior; not copying function length/name.


2) Array.prototype.forEach

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function (cb, thisArg) {
    if (this == null) throw new TypeError('Array.prototype.forEach called on null or undefined');
    const O = Object(this);
    const len = O.length >>> 0;
    for (let i = 0; i < len; i++) if (i in O) cb.call(thisArg, O[i], i, O);
  };
}

Notes: Sparse arrays: skips holes.


3) Array.prototype.map

if (!Array.prototype.map) {
  Array.prototype.map = function (cb, thisArg) {
    if (this == null) throw new TypeError('Array.prototype.map called on null or undefined');
    const O = Object(this), len = O.length >>> 0, A = new Array(len);
    for (let i = 0; i < len; i++) if (i in O) A[i] = cb.call(thisArg, O[i], i, O);
    return A;
  };
}

4) Array.prototype.filter

if (!Array.prototype.filter) {
  Array.prototype.filter = function (cb, thisArg) {
    if (this == null) throw new TypeError('Array.prototype.filter called on null or undefined');
    const O = Object(this), len = O.length >>> 0, res = [];
    for (let i = 0; i < len; i++) if (i in O && cb.call(thisArg, O[i], i, O)) res.push(O[i]);
    return res;
  };
}

5) Array.prototype.reduce

if (!Array.prototype.reduce) {
  Array.prototype.reduce = function (cb, initial) {
    if (this == null) throw new TypeError('Reduce on null/undefined');
    const O = Object(this), len = O.length >>> 0;
    let i = 0, acc;
    if (arguments.length > 1) acc = initial; else {
      while (i < len && !(i in O)) i++;
      if (i >= len) throw new TypeError('Reduce of empty array with no initial value');
      acc = O[i++];
    }
    for (; i < len; i++) if (i in O) acc = cb(acc, O[i], i, O);
    return acc;
  };
}

6) Array.prototype.find

if (!Array.prototype.find) {
  Array.prototype.find = function (predicate, thisArg) {
    if (this == null) throw new TypeError('find called on null/undefined');
    const O = Object(this), len = O.length >>> 0;
    for (let i = 0; i < len; i++) if (i in O && predicate.call(thisArg, O[i], i, O)) return O[i];
    return undefined;
  };
}

7) Array.prototype.findIndex

if (!Array.prototype.findIndex) {
  Array.prototype.findIndex = function (predicate, thisArg) {
    if (this == null) throw new TypeError('findIndex called on null/undefined');
    const O = Object(this), len = O.length >>> 0;
    for (let i = 0; i < len; i++) if (i in O && predicate.call(thisArg, O[i], i, O)) return i;
    return -1;
  };
}

8) Array.prototype.includes

if (!Array.prototype.includes) {
  Array.prototype.includes = function (search, fromIndex = 0) {
    const O = Object(this), len = O.length >>> 0;
    let k = Math.max(fromIndex >= 0 ? fromIndex : len + fromIndex, 0);
    function sameValueZero(x, y) { return x === y || (x !== x && y !== y); }
    for (; k < len; k++) if (sameValueZero(O[k], search)) return true;
    return false;
  };
}

Notes: Uses SameValueZero (matches NaN).


9) Array.prototype.flat (depth = 1)

if (!Array.prototype.flat) {
  Array.prototype.flat = function (depth = 1) {
    const O = Object(this);
    const d = Number(depth) || 0;
    const out = [];
    (function flatInto(arr, depth) {
      for (let i = 0; i < arr.length; i++) if (i in arr) {
        const val = arr[i];
        if (Array.isArray(val) && depth > 0) flatInto(val, depth - 1);
        else out.push(val);
      }
    })(O, d);
    return out;
  };
}

10) Array.prototype.flatMap

if (!Array.prototype.flatMap) {
  Array.prototype.flatMap = function (cb, thisArg) {
    return this.map(cb, thisArg).flat(1);
  };
}

11) Array.from

if (!Array.from) {
  Array.from = function (arrayLike, mapFn, thisArg) {
    const O = Object(arrayLike);
    const len = Math.min(Math.max(Number(O.length) || 0, 0), Number.MAX_SAFE_INTEGER);
    const A = new Array(len);
    for (let k = 0; k < len; k++) {
      const v = O[k];
      A[k] = mapFn ? mapFn.call(thisArg, v, k) : v;
    }
    return A;
  };
}

Notes: Basic; does not handle iterables (pre-ES6 environments lack iterators).


12) Array.isArray

if (!Array.isArray) {
  Array.isArray = function (arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

13) Object.create

if (!Object.create) {
  Object.create = function (proto, propertiesObject) {
    function F() {}
    F.prototype = proto;
    const obj = new F();
    if (propertiesObject) Object.defineProperties(obj, propertiesObject);
    return obj;
  };
}

Notes: Requires Object.defineProperties support; otherwise feature-detect/use simple assign.


14) Object.assign

if (!Object.assign) {
  Object.assign = function (target, ...sources) {
    if (target == null) throw new TypeError('Cannot convert undefined or null to object');
    const to = Object(target);
    for (const src of sources) if (src != null) {
      for (const key in src) if (Object.prototype.hasOwnProperty.call(src, key)) to[key] = src[key];
      if (Object.getOwnPropertySymbols) {
        const syms = Object.getOwnPropertySymbols(src);
        for (const s of syms) if (Object.prototype.propertyIsEnumerable.call(src, s)) to[s] = src[s];
      }
    }
    return to;
  };
}

15) Object.keys

if (!Object.keys) {
  Object.keys = function (obj) {
    if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) throw new TypeError('Object.keys called on non-object');
    const res = [];
    for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
    return res;
  };
}

16) Object.values

if (!Object.values) {
  Object.values = function (obj) {
    return Object.keys(obj).map(k => obj[k]);
  };
}

17) Object.entries

if (!Object.entries) {
  Object.entries = function (obj) {
    return Object.keys(obj).map(k => [k, obj[k]]);
  };
}

18) String.prototype.includes

if (!String.prototype.includes) {
  String.prototype.includes = function (search, start) {
    if (search instanceof RegExp) throw new TypeError('First argument must not be a RegExp');
    return this.indexOf(search, start) !== -1;
  };
}

19) String.prototype.startsWith

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function (search, pos = 0) {
    return this.substr(pos, search.length) === search;
  };
}

20) String.prototype.endsWith

if (!String.prototype.endsWith) {
  String.prototype.endsWith = function (search, thisLen) {
    const str = String(this);
    if (thisLen === undefined || thisLen > str.length) thisLen = str.length;
    return str.substring(thisLen - search.length, thisLen) === search;
  };
}

21) String.prototype.repeat

if (!String.prototype.repeat) {
  String.prototype.repeat = function (count) {
    count = Math.floor(Number(count));
    if (count < 0 || count === Infinity) throw new RangeError('Invalid count value');
    let result = '', str = String(this);
    while (count) {
      if (count & 1) result += str;
      count >>= 1; str += str;
    }
    return result;
  };
}

22) String.prototype.trim

if (!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, '');
  };
}

23) String.prototype.padStart

if (!String.prototype.padStart) {
  String.prototype.padStart = function (targetLength, padString = ' ') {
    let str = String(this);
    targetLength = targetLength >> 0;
    if (str.length >= targetLength) return str;
    let pad = String(padString);
    while (pad.length < targetLength - str.length) pad += pad;
    return pad.slice(0, targetLength - str.length) + str;
  };
}

24) String.prototype.padEnd

if (!String.prototype.padEnd) {
  String.prototype.padEnd = function (targetLength, padString = ' ') {
    let str = String(this);
    targetLength = targetLength >> 0;
    if (str.length >= targetLength) return str;
    let pad = String(padString);
    while (pad.length < targetLength - str.length) pad += pad;
    return str + pad.slice(0, targetLength - str.length);
  };
}

25) Number.isNaN

if (!Number.isNaN) {
  Number.isNaN = function (value) {
    return typeof value === 'number' && value !== value; // NaN is the only value not equal to itself
  };
}

26) Number.isInteger

if (!Number.isInteger) {
  Number.isInteger = function (value) {
    return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
  };
}

27) Promise (Minimal)

Problem: Very small ES6 Promise subset (thenables + basic resolution). Useful to explain mechanics; for production use a real polyfill.

(function (global) {
  if (global.Promise) return;
  function isFn(x) { return typeof x === 'function'; }
  function Promise(executor) {
    if (!isFn(executor)) throw new TypeError('Promise resolver is not a function');
    this._state = 'pending';
    this._value = undefined;
    this._handlers = [];
    const resolve = (val) => { settle(this, 'fulfilled', val); };
    const reject  = (err) => { settle(this, 'rejected', err); };
    try { executor(resolve, reject); } catch (e) { reject(e); }
  }
  function settle(self, state, value) {
    if (self._state !== 'pending') return;
    if (value && (typeof value === 'object' || isFn(value))) {
      let then;
      try { then = value.then; } catch (e) { return settle(self, 'rejected', e); }
      if (isFn(then)) {
        return then.call(value, v => settle(self, 'fulfilled', v), r => settle(self, 'rejected', r));
      }
    }
    self._state = state; self._value = value;
    setTimeout(() => self._handlers.splice(0).forEach(runHandler), 0);
  }
  function runHandler(h) {
    const { promise, onFulfilled, onRejected, parent } = h;
    try {
      if (parent._state === 'fulfilled') {
        promiseResolve(promise, isFn(onFulfilled) ? onFulfilled(parent._value) : parent._value);
      } else {
        if (isFn(onRejected)) promiseResolve(promise, onRejected(parent._value));
        else promiseReject(promise, parent._value);
      }
    } catch (e) { promiseReject(promise, e); }
  }
  function promiseResolve(p, x) { settle(p, 'fulfilled', x); }
  function promiseReject(p, r) { settle(p, 'rejected', r); }
  Promise.prototype.then = function (onFulfilled, onRejected) {
    const next = new Promise(function () {});
    this._handlers.push({ promise: next, onFulfilled, onRejected, parent: this });
    if (this._state !== 'pending') setTimeout(() => this._handlers.splice(0).forEach(runHandler), 0);
    return next;
  };
  Promise.prototype.catch = function (onRejected) { return this.then(undefined, onRejected); };
  global.Promise = Promise;
})(typeof window !== 'undefined' ? window : global);

Notes: Educational; not spec-perfect (no microtasks queue, no finally).


28) Promise.all (on top of minimal Promise)

if (typeof Promise !== 'undefined' && !Promise.all) {
  Promise.all = function (iterable) {
    return new Promise((resolve, reject) => {
      const arr = Array.from(iterable);
      const results = new Array(arr.length);
      let remaining = arr.length;
      if (!remaining) return resolve([]);
      arr.forEach((p, i) => Promise.resolve(p).then(v => {
        results[i] = v; if (--remaining === 0) resolve(results);
      }, reject));
    });
  };
}

29) Promise.race

if (typeof Promise !== 'undefined' && !Promise.race) {
  Promise.race = function (iterable) {
    return new Promise((resolve, reject) => {
      Array.from(iterable).forEach(p => Promise.resolve(p).then(resolve, reject));
    });
  };
}

30) fetch via XMLHttpRequest (very small)

Problem: Implement a tiny fetch returning a Promise that resolves with { ok, status, text/json }.

if (typeof window !== 'undefined' && !window.fetch) {
  window.fetch = function (url, options = {}) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open(options.method || 'GET', url, true);
      for (const h in (options.headers || {})) xhr.setRequestHeader(h, options.headers[h]);
      xhr.onload = function () {
        const headers = xhr.getAllResponseHeaders();
        const res = {
          ok: (xhr.status >= 200 && xhr.status < 300),
          status: xhr.status,
          statusText: xhr.statusText,
          url: 'responseURL' in xhr ? xhr.responseURL : url,
          text: () => Promise.resolve(xhr.responseText),
          json: () => Promise.resolve(JSON.parse(xhr.responseText)),
          headers
        };
        resolve(res);
      };
      xhr.onerror = () => reject(new TypeError('Network request failed'));
      xhr.ontimeout = () => reject(new TypeError('Network request failed (timeout)'));
      xhr.send(options.body || null);
    });
  };
}

Notes: No streaming/AbortController/redirect/cors nuances; good for explaining principles.


31) requestAnimationFrame

(function () {
  let lastTime = 0;
  const vendors = ['ms', 'moz', 'webkit', 'o'];
  for (let x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
  }
  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function (callback) {
      const currTime = Date.now();
      const timeToCall = Math.max(0, 16 - (currTime - lastTime));
      const id = window.setTimeout(() => callback(currTime + timeToCall), timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }
  if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function (id) { clearTimeout(id); };
  }
})();

32) CustomEvent

(function () {
  if (typeof window === 'undefined') return;
  try { new CustomEvent('test'); return; } catch (e) {}
  function CustomEvent(event, params) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    const evt = document.createEvent('CustomEvent');
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
    return evt;
  }
  CustomEvent.prototype = window.Event.prototype;
  window.CustomEvent = CustomEvent;
})();

33) Element.prototype.matches

if (typeof Element !== 'undefined' && !Element.prototype.matches) {
  Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector || function (selector) {
    const nodeList = (this.document || this.ownerDocument).querySelectorAll(selector);
    let i = nodeList.length;
    while (--i >= 0 && nodeList.item(i) !== this) {}
    return i > -1;
  };
}

34) Element.prototype.closest

if (typeof Element !== 'undefined' && !Element.prototype.closest) {
  Element.prototype.closest = function (selector) {
    let el = this;
    while (el && el.nodeType === 1) {
      if (el.matches(selector)) return el;
      el = el.parentElement || el.parentNode;
    }
    return null;
  };
}

35) NodeList.prototype.forEach

if (typeof NodeList !== 'undefined' && !NodeList.prototype.forEach) {
  NodeList.prototype.forEach = Array.prototype.forEach;
}

36) Object.freeze (shallow, fallback)

if (!Object.freeze) {
  Object.freeze = function (obj) { return obj; } // No-op fallback
}

Notes: You cannot fully emulate immutability pre-ES5 without proxies.


37) console.time / console.timeEnd

(function () {
  if (typeof console === 'undefined') return;
  const times = Object.create(null);
  if (!console.time) {
    console.time = function (label = 'default') { times[label] = Date.now(); };
  }
  if (!console.timeEnd) {
    console.timeEnd = function (label = 'default') {
      const diff = Date.now() - (times[label] || Date.now());
      delete times[label];
      console.log(label + ': ' + diff + 'ms');
    };
  }
})();

38) setImmediate (using MessageChannel/postMessage/setTimeout)

(function (global) {
  if (global.setImmediate) return;
  let queue = [], posted = false;
  function flush() { posted = false; const q = queue.slice(); queue.length = 0; q.forEach(fn => fn()); }
  if (typeof MessageChannel !== 'undefined') {
    const ch = new MessageChannel(); ch.port1.onmessage = flush;
    global.setImmediate = fn => { queue.push(fn); if (!posted) { posted = true; ch.port2.postMessage(0); } };
  } else if (global.postMessage && !global.importScripts) {
    const KEY = 'SI' + Math.random();
    global.addEventListener('message', e => { if (e.source === global && e.data === KEY) flush(); }, false);
    global.setImmediate = fn => { queue.push(fn); if (!posted) { posted = true; global.postMessage(KEY, '*'); } };
  } else {
    global.setImmediate = fn => setTimeout(fn, 0);
  }
})(typeof window !== 'undefined' ? window : global);

39) URLSearchParams (minimal)

(function (global) {
  if (global.URLSearchParams) return;
  function decode(str) { return decodeURIComponent(str.replace(/\+/g, ' ')); }
  function URLSearchParams(init = '') {
    this._data = [];
    if (typeof init === 'string') {
      if (init[0] === '?') init = init.slice(1);
      init.split('&').forEach(p => {
        if (!p) return; const [k, v=''] = p.split('=');
        this.append(decode(k), decode(v));
      });
    } else if (init && typeof init === 'object') {
      Object.keys(init).forEach(k => this.append(k, init[k]));
    }
  }
  URLSearchParams.prototype.append = function (k, v) { this._data.push([String(k), String(v)]); };
  URLSearchParams.prototype.get = function (k) { const e = this._data.find(x => x[0] === k); return e ? e[1] : null; };
  URLSearchParams.prototype.set = function (k, v) { this.delete(k); this.append(k, v); };
  URLSearchParams.prototype.delete = function (k) { this._data = this._data.filter(x => x[0] !== k); };
  URLSearchParams.prototype.toString = function () { return this._data.map(([k,v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); };
  global.URLSearchParams = URLSearchParams;
})(typeof window !== 'undefined' ? window : global);

40) atob / btoa (Base64, ASCII only)

(function (global) {
  if (!global.atob) {
    global.atob = function (str) { return decodeURIComponent(escape(window.atob(str))); };
  }
  if (!global.btoa) {
    global.btoa = function (str) { return window.btoa(unescape(encodeURIComponent(str))); };
  }
})(typeof window !== 'undefined' ? window : global);

Notes: ASCII-safe; full Unicode-safe Base64 is more involved.


Interview Tips

  • Always feature-detect before defining.
  • Know behavior on sparse arrays (forEach, map, filter skip holes; reduce can start from first present index).
  • Be ready to discuss spec terms: SameValueZero, callable, thenable, iterator.
  • For big APIs (Promises, fetch), show a minimal educational version and call out missing spec bits (microtask queue, streams, aborts).