Back to all notes

Array Operations: Time and Space Costs

A technical breakdown of array insertions and deletions, why costs vary by operation, and what happens under the hood.

7 min read

Many of us start programming with high level languages. Arrays feel easy, so we move fast.

In the age of AI, a lot of code is written for us. That makes it easy to skip the basics. This post keeps it simple and technical. I use JavaScript to show what array operations do and why their costs change.

Core idea: indexes and shifting

Arrays store items in order. Each item has an index.

const arr = ["A", "B", "C", "D"];
// indexes:   0    1    2    3

Access by index is usually O(1):

arr[2]; // "C"

But inserts and deletes can be expensive. When you change the start or the middle, items after the edit need new indexes. The engine shifts elements left or right. That is the cost.

Most of these methods mutate the array. They also return different values. Some return the removed item. Some return the new length.

Operations at the end (fast)

Add at the end: push()

JavaScript way:

const arr = ["A", "B", "C"];
const newLength = arr.push("D");
// arr is now ["A", "B", "C", "D"]
// newLength is 4

Deconstruction:

function deconstructPush(arr, value) {
  arr[arr.length] = value;
  return arr.length;
}

Post mortem:

  • Operation. Add one element at the end.
  • Under the hood. Append a new slot.
  • Time. Usually O(1). Can spike when the array grows.
  • Extra space. Usually O(1).
  • Returns. New length.

Remove at the end: pop()

JavaScript way:

const arr = ["A", "B", "C"];
const removed = arr.pop();
// arr is now ["A", "B"]
// removed is "C"

Deconstruction:

function deconstructPop(arr) {
  if (arr.length === 0) return undefined;
  const lastIndex = arr.length - 1;
  const value = arr[lastIndex];
  arr.length = lastIndex;
  return value;
}

Post mortem:

  • Operation. Remove one element from the end.
  • Under the hood. Drop the last slot.
  • Time. Usually O(1).
  • Extra space. O(1).
  • Returns. The removed element, or undefined.

Operations at the start (slow)

When you edit index 0, every other item changes index. The array shifts a lot of values.

Add at the start: unshift()

JavaScript way:

const arr = ["B", "C", "D"];
const newLength = arr.unshift("A");
// arr is now ["A", "B", "C", "D"]

Deconstruction:

function deconstructUnshift(arr, value) {
  arr.length = arr.length + 1;
  for (let i = arr.length - 1; i > 0; i--) {
    arr[i] = arr[i - 1];
  }
  arr[0] = value;
  return arr.length;
}

Post mortem:

  • Operation. Add one element at index 0.
  • Under the hood. Shift all existing elements right by one.
  • Time. O(n).
  • Extra space. O(1).
  • Returns. New length.

Remove at the start: shift()

JavaScript way:

const arr = ["A", "B", "C"];
const removed = arr.shift();
// arr is now ["B", "C"]
// removed is "A"

Deconstruction:

function deconstructShift(arr) {
  if (arr.length === 0) return undefined;
  const value = arr[0];
  for (let i = 0; i < arr.length - 1; i++) {
    arr[i] = arr[i + 1];
  }
  arr.length = arr.length - 1;
  return value;
}

Post mortem:

  • Operation. Remove index 0.
  • Under the hood. Shift all remaining elements left by one.
  • Time. O(n).
  • Extra space. O(1).
  • Returns. The removed element, or undefined.

Operations in the middle (slow)

Adding and removing in the middle is slower than the end. Elements must shift to keep indexes correct.

Insert at a position: shift right

Why insert is O(n):

  • Start from the end and move backward.
  • Shift each element one position to the right.
  • Stop at the target index.
  • Place the new element.
  • Update the array length.

JavaScript way:

const numbers = [10, 20, 30, 40];
numbers.splice(2, 0, 99); // insert at index 2
// [10, 20, 99, 30, 40]

Deconstruction:

function deconstructInsertAt(arr, index, value) {
  arr.length = arr.length + 1;
  for (let i = arr.length - 1; i > index; i--) {
    arr[i] = arr[i - 1];
  }
  arr[index] = value;
  return arr;
}

Post mortem:

  • Operation. Insert at a specific index.
  • Under the hood. Shift elements after the index to the right.
  • Time. O(n).
  • Extra space. O(1).
  • Returns. splice() returns an array of removed items. For inserts, that is [].

Delete at a position: shift left

Why delete is O(n):

  • Remove the element at the index.
  • Shift each element after it one position to the left.
  • Update the array length.

JavaScript way:

const numbers = [10, 20, 30, 40, 50];
const removed = numbers.splice(2, 1); // remove index 2
// numbers is [10, 20, 40, 50]
// removed is [30]

Deconstruction:

function deconstructDeleteAt(arr, index) {
  for (let i = index; i < arr.length - 1; i++) {
    arr[i] = arr[i + 1];
  }
  arr.length = arr.length - 1;
  return arr;
}

Post mortem:

  • Operation. Remove a specific index.
  • Under the hood. Shift elements after it to the left.
  • Time. O(n).
  • Extra space. O(1).
  • Returns. splice() returns an array of removed items.

Position matters

The cost depends on where you edit the array:

  • Worst case. Insert or delete at the start. You shift all n elements.
  • Average case. Insert or delete in the middle. You shift about half.
  • Best case. Insert or delete at the end. You shift zero.

Array operations summary

OperationMethodTypical timeTypical extra spaceNotes
Access by indexarr[i]O(1)O(1)direct lookup
Update by indexarr[i] = xO(1)O(1)direct update
Add at endpush()O(1)O(1)returns new length
Remove at endpop()O(1)O(1)returns removed element
Add at startunshift()O(n)O(1)shifts right
Remove at startshift()O(n)O(1)shifts left
Insert at indexsplice(i, 0, v)O(n)O(1)returns []
Remove at indexsplice(i, 1)O(n)O(1)returns removed items
Search unsortedindexOf()O(n)O(1)scans list
Full iterationfor, forEachO(n)O(1)visits all elements

Fast operations, usually O(1):

  • Access by index. arr[i]
  • Update by index. arr[i] = x
  • Append. push()
  • Remove from end. pop()

Slow operations, usually O(n):

  • Search in an unsorted array. indexOf()
  • Insert at the middle. splice()
  • Delete at the middle. splice()
  • Insert at the start. unshift()
  • Delete at the start. shift()
  • Iteration. Any full pass over the array

When to avoid middle operations

Avoid middle inserts and deletes when:

  • You do many updates in a loop.
  • The array is large.
  • Performance is sensitive.

Alternatives and tradeoffs

Swap and pop

If order does not matter, you can delete in O(1).

JavaScript way:

function swapAndPop(arr, index) {
  arr[index] = arr[arr.length - 1];
  arr.pop();
  return arr;
}

Deconstruction:

function deconstructSwapAndPop(arr, index) {
  const lastIndex = arr.length - 1;
  arr[index] = arr[lastIndex];
  arr.length = lastIndex;
  return arr;
}

Post mortem:

  • Operation. Remove one element at an index.
  • Under the hood. Replace it with the last element, then drop the last slot.
  • Time. O(1).
  • Cost. Order changes.

Linked lists

If you do frequent inserts and deletes, a linked list can help. Inserts and deletes are O(1) once you have the node. Finding that node is still O(n).

We will discuss linked lists in a JavaScript way in a separate post so the full scenario is clear. That will cover why linked lists are needed and how they help. This article is about arrays, but it still builds the mental model that makes linked lists make sense.