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,filterskip holes;reducecan 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).