Advanced JS

Custom JavaScript Array Method Implementations

Recreate Array.prototype methods including concat, splice, map, reduce, find, includes, every, and some to understand JavaScript array behavior.

Custom Implementations of JavaScript Array Methods

This page rebuilds Array.prototype methods such as concat(), fill(), splice(), map(), reduce(), find(), includes(), every(), and some(). The examples focus on how array methods handle mutation, copying, callbacks, indexes, search, predicates, and return values.

Array.concat

The concat() method merges two or more arrays and returns a new array without modifying the original. It also handles non-array values by appending them as-is. This custom implementation replicates native behavior by flattening one level of array input while preserving nested structures.

Array.prototype.myConcat = function (...arrays) {
  const result = [...this];

  for (const array of arrays) {
    if (Array.isArray(array)) {
      result.push(...array);
    } else {
      result.push(array);
    }
  }

  return result;
};

const arr = [1, 2, 3];
const arr2 = [4, 5, 6, [1]];
const arr3 = [7, 8, 9];
const concat = arr.myConcat(arr2, arr3, 10);
console.log(concat);
// [1, 2, 3, 4, 5, 6, [1], 7, 8, 9, 10]

Array.fill

The fill() method changes all elements in an array between the specified start and end indices to a static value, in place. It returns the modified array and is useful for quickly initializing or resetting arrays.

Array.prototype.customFill = function (value, start = 0, end = this.length) {
  if (start < 0) {
    start = this.length + start;
  }

  if (end < 0) {
    end = this.length + end;
  }

  for (let i = start; i < Math.min(end, this.length); i++) {
    this[i] = value;
  }

  return this;
};

const array1 = [1, 2, 3, 4];

// Fill with 0 from position 2 until position 4
console.log(array1.customFill(0, 2, 4));
// [1, 2, 0, 0]

// Fill with 5 from position 1
console.log(array1.customFill(5, 1));
// [1, 5, 5, 5]

console.log(array1.customFill(6));
// [6, 6, 6, 6]

console.log([1, 2, 3].customFill(4)); // [4, 4, 4]
console.log([1, 2, 3].customFill(4, 1)); // [1, 4, 4]
console.log([1, 2, 3].customFill(4, 1, 2)); // [1, 4, 3]
console.log([1, 2, 3].customFill(4, 1, 1)); // [1, 2, 3]
console.log([1, 2, 3].customFill(4, 3, 3)); // [1, 2, 3]
console.log([1, 2, 3].customFill(4, -3, -2)); // [4, 2, 3]

Array.pop

The pop() method removes the last element from an array and returns it. It mutates the original array by reducing its length by one.

Array.prototype.customPop = function () {
  const length = this.length;

  if (length === 0) {
    return undefined;
  }

  const lastElement = this[length - 1];
  this.length = length - 1;

  return lastElement;
};

const fruits = ["Apple", "Banana", "Cherry"];
const lastFruit = fruits.customPop();
console.log(lastFruit); // Cherry
console.log(fruits); // [ "Apple", "Banana" ]

Array.push

The push() method adds one or more elements to the end of an array and returns the new length of the array. It's a common and efficient way to grow an array in-place.

Array.prototype.customPush = function () {
  for (let i = 0; i < arguments.length; i++) {
    this[this.length] = arguments[i];
  }

  return this.length;
};

const animals = ["pigs", "goats"];

const count = animals.customPush("cows");
console.log(count); // 3

console.log(animals); // [ 'pigs', 'goats', 'cows' ]

animals.customPush("chickens", "cats");
console.log(animals);
// [ 'pigs', 'goats', 'cows', 'chickens', 'cats' ]

Array.reverse

The reverse() method reverses an array in place and returns the modified array. The first element becomes the last, and the last becomes the first, effectively reversing the array's order.

Array.prototype.customReverse = function () {
  const middle = Math.floor(this.length / 2);

  for (let i = 0; i < middle; i++) {
    const temp = this[i];
    this[i] = this[this.length - 1 - i];
    this[this.length - 1 - i] = temp;
  }

  return this;
};

const array1 = ["one", "two", "three", "four"];

const reversed = array1.customReverse();
console.log(reversed); // [ 'four', 'three', 'two', 'one' ]
// reverse changes the original array
console.log(array1); // [ 'four', 'three', 'two', 'one' ]

Array.shift

The shift() method removes the first element from an array and returns it, shifting the remaining elements down. This method mutates the array by reducing its length.

Array.prototype.customShift = function () {
  if (!this.length) return;

  const firstElement = this[0];

  for (let i = 0; i < this.length; i++) {
    this[i] = this[i + 1];
  }

  this.length -= 1;

  return firstElement;
};

const array1 = [1, 2, 3];

const firstElement = array1.customShift();

console.log(array1); // [2, 3]
console.log(firstElement); // 1

Array.unshift

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

In this custom implementation, the method first shifts all existing elements to the right to make space for the new elements. Then, it inserts the new elements at the beginning of the array and returns the updated length.

Array.prototype.customUnshift = function (...elements) {
  const originalLength = this.length;
  const totalLength = elements.length + originalLength;

  // Shift existing elements to the right
  for (let i = originalLength - 1; i >= 0; i--) {
    this[i + elements.length] = this[i];
  }

  // Add new elements at the beginning
  for (let i = 0; i < elements.length; i++) {
    this[i] = elements[i];
  }

  return totalLength; // Return the new length
};

const arr = [3, 4, 5];
arr.customUnshift(1, 2);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(arr.length); // 5

Array.splice

The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.

Array.prototype.customSplice = function (
  startIndex,
  deleteCount,
  ...itemsToAdd
) {
  const length = this.length;

  // Handle negative indices
  startIndex =
    startIndex < 0
      ? Math.max(length + startIndex, 0)
      : Math.min(startIndex, length);

  // If deleteCount is undefined, remove all elements starting from startIndex
  if (deleteCount === undefined) {
    deleteCount = length - startIndex;
  } else {
    // Normalize deleteCount
    deleteCount = Math.max(0, Math.min(deleteCount, length - startIndex));
  }

  // Extract the array to be deleted
  const splicedItems = this.slice(startIndex, startIndex + deleteCount);

  // Create the resulting this by combining parts and items to add
  const remainingItems = [
    ...this.slice(0, startIndex),
    ...itemsToAdd,
    ...this.slice(startIndex + deleteCount),
  ];

  // Update the original array
  this.length = 0; // Clear the this
  for (let i = 0; i < remainingItems.length; i++) {
    this[i] = remainingItems[i];
  }

  // Return the deleted items
  return splicedItems;
};

// Remove 0 (zero) elements before index 2, and insert "drum"
const myFish = ["angel", "clown", "mandarin", "sturgeon"];
const removed = myFish.customSplice(2, 0, "drum");
console.log({ myFish, removed });
// myFish: [ 'angel', 'clown', 'drum', 'mandarin', 'sturgeon' ]
// removed: []

// Remove 0 (zero) elements before index 2, and insert "drum" and "guitar"
const myFish1 = ["angel", "clown", "mandarin", "sturgeon"];
const removed1 = myFish1.customSplice(2, 0, "drum", "guitar");
console.log({ myFish1, removed1 });
// myFish1: [ 'angel', 'clown', 'drum', 'guitar', 'mandarin', 'sturgeon' ],
// removed1: []

// Remove 0 (zero) elements at index 0, and insert "angel"
// splice(0, 0, ...elements) inserts elements at the start of
// the array like unshift().
const myFish2 = ["clown", "mandarin", "sturgeon"];
const removed2 = myFish2.customSplice(0, 0, "angel");
console.log({ myFish2, removed2 });
// myFish2: [ 'angel', 'clown', 'mandarin', 'sturgeon' ], removed2: []

// Remove 0 (zero) elements at last index, and insert "sturgeon"
// splice(array.length, 0, ...elements) inserts elements at the
// end of the array like push().
const myFish3 = ["angel", "clown", "mandarin"];
const removed3 = myFish3.customSplice(myFish3.length, 0, "sturgeon");
console.log({ myFish3, removed3 });
// myFish3: [ 'angel', 'clown', 'mandarin', 'sturgeon' ], removed3: []

// Remove 1 element at index 3
const myFish4 = ["angel", "clown", "drum", "mandarin", "sturgeon"];
const removed4 = myFish4.customSplice(3, 1);
console.log({ myFish4, removed4 });
// myFish4: [ 'angel', 'clown', 'drum', 'sturgeon' ],
// removed4: [ 'mandarin' ]

// Remove 1 element at index 2, and insert "trumpet"
const myFish5 = ["angel", "clown", "drum", "sturgeon"];
const removed5 = myFish5.customSplice(2, 1, "trumpet");
console.log({ myFish5, removed5 });
// myFish5: [ 'angel', 'clown', 'trumpet', 'sturgeon' ],
// removed5: [ 'drum' ]

// Remove 2 elements from index 0, and insert "parrot", "anemone" and "blue"
const myFish6 = ["angel", "clown", "trumpet", "sturgeon"];
const removed6 = myFish6.customSplice(0, 2, "parrot", "anemone", "blue");
console.log({ myFish6, removed6 });
// myFish6: [ 'parrot', 'anemone', 'blue', 'trumpet', 'sturgeon' ],
// removed6: [ 'angel', 'clown' ]

// Remove 2 elements, starting from index 2
const myFish7 = ["parrot", "anemone", "blue", "trumpet", "sturgeon"];
const removed7 = myFish7.customSplice(2, 2);
console.log({ myFish7, removed7 });
// myFish7: [ 'parrot', 'anemone', 'sturgeon' ],
// removed7: [ 'blue', 'trumpet' ]

// Remove 1 element from index -2
const myFish8 = ["angel", "clown", "mandarin", "sturgeon"];
const removed8 = myFish8.customSplice(-2, 1);
console.log({ myFish8, removed8 });
// myFish8: [ 'angel', 'clown', 'sturgeon' ], removed8: [ 'mandarin' ]

// Remove all elements, starting from index 2
const myFish9 = ["angel", "clown", "mandarin", "sturgeon"];
const removed9 = myFish9.customSplice(2);
console.log({ myFish9, removed9 });
// myFish9: [ 'angel', 'clown' ], removed9: [ 'mandarin', 'sturgeon' ]

Array.filter

The filter() method creates a shallow copy of an array, including only elements that pass the test implemented by the provided callback function. It’s ideal for narrowing down data based on conditions, like filtering by length, type, or custom logic.

Array.prototype.myFilter = function (callback) {
  const result = [];

  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) {
      result.push(this[i]);
    }
  }

  return result;
};

const words = ["spray", "elite", "exuberant", "destruction", "present"];

const result = words.myFilter((word) => word.length > 6);

console.log(result);
// ["exuberant", "destruction", "present"]

const array = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function isPrime(num) {
  for (let i = 2; num > i; i++) {
    if (num % i === 0) {
      return false;
    }
  }
  return num > 1;
}

console.log(array.myFilter(isPrime)); // [ 2, 3, 5, 7 ]

Array.flat

The flat() method creates a new array with all sub-array elements recursively flattened up to the specified depth. It's useful for simplifying nested arrays into a single-level array — or flattening deeply nested structures with Infinity.

Array.prototype.customFlat = function (depth = 1) {
  const result = [];

  const flatten = (array, depth) => {
    for (const item of array) {
      if (Array.isArray(item) && depth > 0) {
        flatten(item, depth - 1);
      } else {
        result.push(item);
      }
    }
  };
  flatten(this, depth);

  return result;
};

const arr = [0, 1, [2, [3, [4, 5]]]];
console.log(arr.customFlat());
// [0, 1, 2, [3, [4, 5]]]
console.log(arr.customFlat(2));
// [0, 1, 2, 3, [4, 5]]
console.log(arr.customFlat(Infinity));
// [0, 1, 2, 3, 4, 5]

Array.flatMap

The flatMap() method first maps each element using a given callback function, then flattens the result by one level. It behaves like array.map(...).flat(), but is more efficient and expressive for nested transformations.

Array.prototype.customFlatMap = function (callback, thisArg) {
  const result = [];

  for (let i = 0; i < this.length; i++) {
    const mapped = callback.call(thisArg, this[i], i, this);

    if (Array.isArray(mapped)) {
      result.push(...mapped); // Use spread operator for flattening
    } else {
      result.push(mapped);
    }
  }

  return result;
};

const data = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
      },
      {
        id: 5,
        name: "Subcategory A2",
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
      },
      {
        id: 12,
        name: "Subcategory B2",
      },
    ],
  },
];

const items = data
  .map((category) =>
    category.items.map((item) => ({
      ...item,
      category: category.name,
    })),
  )
  .flat();
console.log(items);
// [
//   { id: 2, name: 'Subcategory A1', category: 'Category A' },
//   { id: 5, name: 'Subcategory A2', category: 'Category A' },
//   { id: 9, name: 'Subcategory B1', category: 'Category B' },
//   { id: 12, name: 'Subcategory B2', category: 'Category B' }
// ]

const items2 = data.customFlatMap((category) =>
  category.items.map((item) => ({
    ...item,
    category: category.name,
  })),
);
console.log(items2);
// ...same result

Array.join

The join() method creates and returns a string by concatenating all elements of an array, separated by a specified string (default is a comma). If the array has only one item, that item is returned as-is. It’s useful for formatting output or combining array values into CSV-like strings.

Array.prototype.customJoin = function (separator = ",") {
  let result = "";

  for (let i = 0; i < this.length; i++) {
    if (i > 0) {
      result += separator;
    }

    result += this[i];
  }

  return result;
};

const elements = ["Fire", "Air", "Water"];
console.log(elements.customJoin()); // "Fire,Air,Water"
console.log(elements.customJoin("")); // "FireAirWater"
console.log(elements.customJoin("-")); // "Fire-Air-Water"

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];
console.log(matrix.customJoin()); // 1,2,3,4,5,6,7,8,9
console.log(matrix.customJoin(";")); // 1,2,3;4,5,6;7,8,9

Array.map

The map() method creates a new array populated with the results of calling a provided function on every element in the original array. It's a key tool for transforming data structures in a clean, functional style — especially for UI rendering, data formatting, or calculations.

Array.prototype.customMap = function (callbackFn) {
  if (typeof callbackFn !== "function") {
    throw new TypeError("Callback must be a function");
  }

  const arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(callbackFn(this[i], i, this));
  }

  return arr;
};

const array1 = [1, 4, 9, 16];
const doubled = array1.customMap((x) => x * 2);
console.log(doubled); // [ 2, 8, 18, 32 ]

const kvArray = [
  { key: 1, value: 10 },
  { key: 2, value: 20 },
  { key: 3, value: 30 },
];
const formattedArray = kvArray.customMap(({ key, value }) => ({
  [key]: value,
}));
console.log(formattedArray); // [ { 1: 10 }, { 2: 20 }, { 3: 30 } ]

console.log(["1", "2", "3"].customMap(Number)); // [ 1, 2, 3 ]

const numbers2 = [1, 2, 3, 4, 5];
const cumulativeSum = numbers2.customMap((num, idx, arr) => {
  // Calculate cumulative sum by adding the current number to the sum of previous numbers
  const previousSum =
    idx > 0 ? arr.slice(0, idx).reduce((acc, curr) => acc + curr, 0) : 0;
  return previousSum + num;
});
console.log(cumulativeSum); // [ 1, 3, 6, 10, 15 ]

Array.reduce

The reduce() method executes a user-defined "reducer" callback function on each element of the array, in order. It accumulates a single value by applying the callback function and passing the result from one iteration to the next.

The method can accept an optional initial value. If no initial value is provided, the first array element becomes the accumulator and iteration starts at the second element.

Array.prototype.customReduce = function (callback, initialValue) {
  let accumulator = initialValue !== undefined ? initialValue : this[0];

  const startIndex = initialValue !== undefined ? 0 : 1;

  for (let i = startIndex; i < this.length; i++) {
    accumulator = callback(accumulator, this[i], i, this);
  }

  return accumulator;
};

const array1 = [15, 16, 17, 18, 19];

const sumWithInitial = array1.customReduce((acc, cur) => acc + cur, 0);
console.log(sumWithInitial); // 85

const sumWithoutInitial = array1.customReduce((acc, cur) => acc + cur);
console.log(sumWithoutInitial); // 85

const pipe =
  (...functions) =>
  (initialValue) =>
    functions.customReduce((acc, fn) => fn(acc), initialValue);

const double = (x) => x * 2;
const triple = (x) => x * 3;

const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);

console.log(multiply6(6)); // 36
console.log(multiply9(9)); // 81

const asyncPipe =
  (...functions) =>
  (initialValue) =>
    functions.reduce((acc, fn) => acc.then(fn), Promise.resolve(initialValue));

const p1 = async (a) => a * 5;
const p2 = async (a) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(a * 2), 1000);
  });
const f3 = (a) => a * 3;
const p4 = async (a) => a * 4;

asyncPipe(p1, p2, f3, p4)(10).then(console.log); // 1200

const asyncPipeAsync =
  (...functions) =>
  (initialValue) =>
    functions.reduce(async (acc, fn) => fn(await acc), initialValue);

asyncPipeAsync(p1, p2, f3, p4)(10).then(console.log); // 1200

Array.slice

The slice() method returns a shallow copy of a portion of an array into a new array, without modifying the original array. The selection is made from start index to end index (excluding the end element).

This custom implementation of Array.prototype.slice() replicates the native behavior, with support for negative indexing.

Array.prototype.customSlice = function (start = 0, end) {
  const length = this.length;
  let endIndex = end || length;

  if (start < 0) {
    start = Math.max(length + start, 0);
  }
  if (endIndex < 0) {
    endIndex = Math.max(length + endIndex, 0);
  }

  const result = [];

  for (let i = start; i < endIndex && i < length; i++) {
    result.push(this[i]);
  }

  return result;
};

const animals = ["ant", "bison", "camel", "duck", "elephant"];

console.log(animals.customSlice(2));
// ["camel", "duck", "elephant"]
console.log(animals.customSlice(2, 4)); // ["camel", "duck"]
console.log(animals.customSlice(-2)); // ["duck", "elephant"]
console.log(animals.customSlice(2, -1)); // ["camel", "duck"]
console.log(animals.customSlice());
// ["ant", "bison", "camel", "duck", "elephant"]

Array.find

The find method returns the first element in the array that satisfies the provided testing function. If no match is found, it returns undefined. The key detail is that it stops at the first match and returns the value, not the index.

Array.prototype.customFind = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if (callback(this[i])) {
      return this[i];
    }
  }
};

const array1 = [5, 12, 8, 130, 44];
const found = array1.customFind((element) => element > 10);
console.log(found); // 12

const inventory = [
  { name: "apples", quantity: 2 },
  { name: "bananas", quantity: 0 },
  { name: "cherries", quantity: 5 },
];

const result = inventory.customFind(({ name }) => name === "cherries");
console.log(result); // { name: 'cherries', quantity: 5 }

const result1 = inventory.customFind(({ name }) => name === "nothing");
console.log(result1); // undefined

Array.findLast

The findLast() method iterates the array in reverse and returns the first value that satisfies the given condition. If no element matches, undefined is returned. Useful when you want the last matching item in an array without reversing it manually.

Array.prototype.customFindLast = function (callback) {
  for (let i = this.length; i >= 0; i--) {
    if (callback(this[i])) {
      return this[i];
    }
  }
};

const array1 = [5, 12, 8, 130, 44];
const found = array1.customFindLast((element) => element > 10);
console.log(found); // 44

Array.at

The at() method takes an integer and returns the item at that index — supporting both positive and negative integers. Negative values count from the end of the array, making it cleaner than using array.length - index.

The custom version supports negative indexes by counting backward from the end of the array.

Array.prototype.customAt = function (index) {
  if (index < 0) {
    index = this.length + index;
  }

  return this[index];
};

const array1 = [5, 12, 8, 130, 44];
console.log(array1.customAt(-1)); // 44

Array.every

The every() method tests whether all elements in an array pass the test implemented by the provided callback. It returns a booleantrue if all elements satisfy the condition, and false otherwise.

The custom version stops as soon as one item fails the predicate. The subset example uses that behavior to check whether every required value exists in another array.

Array.prototype.customEvery = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if (!callback(this[i], i)) {
      return false;
    }
  }

  return true;
};

const arr = [1, 30, 39, 29, 10, 13];

console.log(arr.customEvery((currentValue) => currentValue > 40));
// false
console.log(arr.customEvery((currentValue) => currentValue < 40));
// true

const isSubset = (array1, array2) =>
  array2.customEvery((element) => array1.includes(element));

console.log(isSubset([1, 2, 3, 4, 5, 6, 7], [5, 7, 6])); // true
console.log(isSubset([1, 2, 3, 4, 5, 6, 7], [5, 8, 7])); // false

Array.includes

The includes() method checks if an array contains a specific value, returning true or false. It uses the SameValueZero equality comparison — meaning it treats NaN as equal to NaN.

The custom version includes optional fromIndex support and negative indexing behavior.

function sameValueZero(x, y) {
  return (
    x === y ||
    (typeof x === "number" && typeof y === "number" && x !== x && y !== y)
  );
}

Array.prototype.customIncludes = function (searchElement, fromIndex = 0) {
  const length = this.length;

  if (length === 0) {
    return false;
  }

  if (fromIndex < 0) {
    fromIndex = Math.max(length + fromIndex, 0);
  }

  for (let i = fromIndex; i < length; i++) {
    if (sameValueZero(this[i], searchElement)) {
      return true;
    }
  }

  return false;
};

console.log([1, 2, 3].customIncludes(2)); // true
console.log([1, 2, 3].customIncludes(4)); // false
console.log([1, 2, 3].customIncludes(3, 3)); // false
console.log([1, 2, 3].customIncludes(3, -1)); // true
console.log([1, 2, NaN].customIncludes(NaN)); // true
console.log(["1", "2", "3"].customIncludes(3)); // false

const arr = ["a", "b", "c"];
// Since -100 is much less than the array length,
// it starts checking from index 0.
console.log(arr.customIncludes("a", -100)); // true
console.log(arr.customIncludes("a", -2)); // false
console.log(arr.customIncludes("a", -3)); // true

Array.some

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if any element satisfies the condition; otherwise, it returns false. This method does not modify the original array.

This custom implementation of Array.prototype.some() iterates through the array and returns true if any element passes the provided test.

Array.prototype.customSome = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) {
      return true;
    }
  }

  return false;
};

const array = [1, 2, 3, 4, 5];

const even = (element) => element % 2 === 0;
console.log(array.customSome(even)); // true

const equal90 = (element) => element === 90;
console.log(array.customSome(equal90)); // false

On this page