Build Your Own Array Helpers — Simple vs. Advanced (Interview-Ready)
This markdown gives you two versions for each array method: a simple, easy-to-remember version for interviews, and a more advanced version with stronger edge-case handling. All methods accept a single callback (no thisArg).
Methods covered:
- forEach
- map
- filter
- reduce
Notes:
- “Holes” are missing indices in sparse arrays (e.g., [1, , 3]). Native methods skip them when iterating. We’ll mirror that with if (i in arr).
- In production, prefer the built-ins or well-tested polyfills and avoid modifying Array.prototype.
forEach
Simple (Interview-friendly)
- Assumes normal arrays.
- Skips holes to match native forEach.
- Minimal validation.
if (!Array.prototype.myForEach) {
Array.prototype.myForEach = function (callback) {
if (typeof callback !== "function") {
throw new TypeError("callback must be a function");
}
const arr = this;
const len = arr.length;
for (let i = 0; i < len; i++) {
if (i in arr) { // skip holes
callback(arr[i], i, arr);
}
}
// returns undefined like native
};
}
Usage:
["ali","hamza","jack"].myForEach((v, i) => console.log(i, v));
Advanced (More robust)
- Guards against null/undefined receiver.
- Validates callback.
- Skips holes, handles array-like via Object(this), normalizes length.
if (!Array.prototype.myForEach) {
Array.prototype.myForEach = function (callback) {
if (this == null) throw new TypeError("Array.prototype.myForEach called on null or undefined");
if (typeof callback !== "function") throw new TypeError("callback must be a function");
const O = Object(this);
const len = O.length >>> 0; // normalize to uint32
for (let i = 0; i < len; i++) {
if (Object.prototype.hasOwnProperty.call(O, i)) { // skip holes
callback(O[i], i, O);
}
}
};
}
map
Simple (Interview-friendly)
- Preserves holes and output indices by assigning into a pre-sized array.
- Minimal validation.
if (!Array.prototype.myMap) {
Array.prototype.myMap = function (callback) {
if (typeof callback !== "function") {
throw new TypeError("callback must be a function");
}
const arr = this;
const len = arr.length;
const out = new Array(len);
for (let i = 0; i < len; i++) {
if (i in arr) { // skip holes when reading
out[i] = callback(arr[i], i, arr);
}
// else leave hole in out
}
return out;
};
}
Usage:
const mapped = [1, , 3].myMap(x => x * 10);
console.log(mapped); // [10, <1 empty item>, 30]
console.log(1 in mapped); // false
Advanced (More robust)
- Guards against null/undefined receiver.
- Validates callback.
- Supports array-like via Object(this), normalizes length, skips holes, preserves holes in result.
if (!Array.prototype.myMap) {
Array.prototype.myMap = function (callback) {
if (this == null) throw new TypeError("Array.prototype.myMap called on null or undefined");
if (typeof callback !== "function") throw new TypeError("callback must be a function");
const O = Object(this);
const len = O.length >>> 0;
const A = new Array(len);
for (let i = 0; i < len; i++) {
if (Object.prototype.hasOwnProperty.call(O, i)) {
A[i] = callback(O[i], i, O);
}
// else leave hole
}
return A;
};
}
filter
Simple (Interview-friendly)
- Skips holes during iteration.
- Minimal validation.
if (!Array.prototype.myFilter) {
Array.prototype.myFilter = function (callback) {
if (typeof callback !== "function") {
throw new TypeError("callback must be a function");
}
const arr = this;
const len = arr.length;
const out = [];
for (let i = 0; i < len; i++) {
if (i in arr) { // skip holes
const v = arr[i];
if (callback(v, i, arr)) out.push(v);
}
}
return out;
};
}
Usage:
console.log([1, , 3, 4].myFilter(v => v > 2)); // [3, 4]
Advanced (More robust)
- Guards against null/undefined receiver.
- Validates callback.
- Supports array-like via Object(this), normalizes length, skips holes.
if (!Array.prototype.myFilter) {
Array.prototype.myFilter = function (callback) {
if (this == null) throw new TypeError("Array.prototype.myFilter called on null or undefined");
if (typeof callback !== "function") throw new TypeError("callback must be a function");
const O = Object(this);
const len = O.length >>> 0;
const res = [];
for (let i = 0; i < len; i++) {
if (Object.prototype.hasOwnProperty.call(O, i)) {
const v = O[i];
if (callback(v, i, O)) res.push(v);
}
}
return res;
};
}
reduce
Simple (Interview-friendly)
- Handles initialValue correctly using arguments.length.
- Skips holes.
- Minimal validation.
if (!Array.prototype.myReduce) {
Array.prototype.myReduce = function (callback, initialValue) {
if (typeof callback !== "function") {
throw new TypeError("callback must be a function");
}
const arr = this;
const len = arr.length;
let i = 0;
let acc;
if (arguments.length > 1) {
acc = initialValue;
} else {
// find first present element as initial acc
while (i < len && !(i in arr)) i++;
if (i >= len) throw new TypeError("Reduce of empty array with no initial value");
acc = arr[i++];
}
for (; i < len; i++) {
if (i in arr) {
acc = callback(acc, arr[i], i, arr);
}
}
return acc;
};
}
Usage:
console.log([1,2,3].myReduce((a,v)=>a+v)); // 6
console.log([,2,,4].myReduce((a,v)=>a+v, 0)); // 6 (skips holes)
Advanced (More robust)
- Guards against null/undefined receiver.
- Validates callback.
- Supports array-like via Object(this), normalizes length, skips holes.
- Throws on empty arrays without initialValue.
if (!Array.prototype.myReduce) {
Array.prototype.myReduce = function (callback, initialValue) {
if (this == null) throw new TypeError("Array.prototype.myReduce called on null or undefined");
if (typeof callback !== "function") throw new TypeError("callback must be a function");
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
let acc;
if (arguments.length > 1) {
acc = initialValue;
} else {
while (k < len && !Object.prototype.hasOwnProperty.call(O, k)) k++;
if (k >= len) throw new TypeError("Reduce of empty array with no initial value");
acc = O[k++];
}
for (; k < len; k++) {
if (Object.prototype.hasOwnProperty.call(O, k)) {
acc = callback(acc, O[k], k, O);
}
}
return acc;
};
}
Extras: Passing additional data to callbacks (without thisArg)
Because we’re using single-argument callbacks, pass extra parameters via closures or bind:
- Closure factory:
const multiplyBy = (factor) => (n) => n * factor;
console.log([1,2,3].myMap(multiplyBy(10))); // [10,20,30]
- Bind fixed args (ignoring this):
function between(min, max, x) { return x >= min && x <= max; }
const inRange = between.bind(null, 3, 6);
console.log([1,3,5,7].myFilter(inRange)); // [3,5]
- Reduce with a cap via closure:
const cappedSum = (cap) => (acc, v) => Math.min(acc + v, cap);
console.log([2,5,10].myReduce(cappedSum(12), 0)); // 12
Top comments (0)