ArraysLesson 2

Array Operations

Insert, delete, search, traverse, update — every fundamental array operation with step-by-step walkthroughs and complexity analysis.

What Is It?

Knowing how an array is stored in memory is step one. Step two is understanding what you can do with it.

Arrays support six core operations: access (get an element by index), search (find an element by value), insert (add a new element), delete (remove an element), traverse (visit every element), and update (change a value at an index).

Some of these are instant. Others slow down as the array gets bigger. Knowing the difference is what lets you write fast programs.

Analogy

The Bookshelf

Imagine a bookshelf with 10 numbered slots, packed tightly left to right with no gaps.

  • Access: "Give me the book in slot 5." Instant — you walk straight to it. O(1) — always fast, no matter the size.
  • Search: "Do you have Moby Dick?" You scan from slot 0 through every book until you find it or reach the end. O(n) — takes longer as the shelf gets bigger.
  • Insert at end: Put a new book in the first empty slot. O(1) — always fast.
  • Insert in middle: "Put this book in slot 3." You must slide every book from slot 3 onward one spot to the right. O(n) — takes longer as the shelf gets bigger.
  • Delete from middle: Remove the book at slot 3. Now there's a gap. Slide every book after it one spot left. O(n) — takes longer as the shelf gets bigger.
  • Update: Replace the book in slot 5. Walk there, swap. O(1) — always fast.

How It Works

ACCESS — O(1), always fast

Accessing an element by index uses the address formula from the previous lesson. One calculation, done.

pseudocode
1
2
3
4
function access(array, index):
if index < 0 or index >= array.length:
error "Index out of bounds"
return array[index]

Trace with array = [10, 20, 30, 40, 50], accessing index 2:

Bounds check
valid
Return value
301 op

SEARCH — O(n), takes longer as the array gets bigger

To find an element by value (not index), you have to check each element one by one. You don't know where it is.

pseudocode
1
2
3
4
5
function linearSearch(array, target):
for i = 0 to array.length - 1:
if array[i] == target:
return i // found it at index i
return -1 // not in the array

Trace with array = [10, 20, 30, 40, 50], searching for 30:

i = 0
Noskip
i = 1
Noskip
i = 2
Yesfound!

In the worst case, the target is at the very end (or not there at all). You check every element — that's n checks for n elements.

INSERT — O(1) at the end, O(n) anywhere else

Adding to the end (if there's room) is simple:

pseudocode
1
2
3
4
5
function insertAtEnd(array, value, currentSize):
if currentSize >= array.capacity:
error "Array is full"
array[currentSize] = value
currentSize = currentSize + 1

Inserting in the middle is expensive. You have to shift everything after the insertion point one slot to the right to make room:

pseudocode
1
2
3
4
5
6
7
8
9
10
11
12
function insertAt(array, index, value, currentSize):
if currentSize >= array.capacity:
error "Array is full"
if index < 0 or index > currentSize:
error "Invalid index"
// Shift elements right, starting from the end
for i = currentSize - 1 down to index:
array[i + 1] = array[i]
array[index] = value
currentSize = currentSize + 1

Step-by-step trace — inserting 25 at index 2 in [10, 20, 30, 40, 50]:

Insert 25 at index 2step through the shifts
1 / 6
Before — insert 25 at index 2
10
20
30
40
50
__
[0]
[1]
[2]
[3]
[4]
[5]

We shifted 3 elements. Inserting at the very beginning shifts ALL elements — that's the worst case.

Beforeinsert 5 at index 0
10
20
30
40
50
[0]
[1]
[2]
[3]
[4]

Shift every element one slot to the right:

After Shiftall elements moved right
10
20
30
40
50
[0]
[1]
[2]
[3]
[4]
[5]

Write 5 at index 0:

After Write5 inserted at index 0
5
10
20
30
40
50
[0]
[1]
[2]
[3]
[4]
[5]

DELETE — O(1) at the end, O(n) anywhere else

Removing the last element is trivial:

pseudocode
1
2
3
4
5
function deleteAtEnd(array, currentSize):
if currentSize == 0:
error "Array is empty"
currentSize = currentSize - 1
// The value is still in memory, but we treat it as outside the valid range

Deleting from the middle requires shifting elements left to fill the gap:

pseudocode
1
2
3
4
5
6
7
8
9
function deleteAt(array, index, currentSize):
if index < 0 or index >= currentSize:
error "Invalid index"
// Shift elements left, starting from the deletion point
for i = index to currentSize - 2:
array[i] = array[i + 1]
currentSize = currentSize - 1

Step-by-step trace — deleting element at index 1 from [10, 20, 30, 40, 50]:

Delete at index 1step through the shifts
1 / 5
Before — delete element at index 1 (value 20)
10
20
30
40
50
[0]
[1]
[2]
[3]
[4]

TRAVERSE — O(n), takes longer as the array gets bigger

Visiting every element. Forward or backward:

pseudocode
1
2
3
4
5
6
7
function traverseForward(array, size):
for i = 0 to size - 1:
visit(array[i])
function traverseBackward(array, size):
for i = size - 1 down to 0:
visit(array[i])

You visit each element once, so it always takes n steps for n elements.

UPDATE — O(1), always fast

Changing a value at a known index:

pseudocode
1
2
3
4
function update(array, index, newValue):
if index < 0 or index >= array.length:
error "Index out of bounds"
array[index] = newValue

Same as access — you jump directly to the position and overwrite it.

Operation Summary

Access by index
O(1)
Search by value
O(n)worst
Insert at end
O(1)
Insert at beginning
O(n)
Insert at middle
O(n)
Delete at end
O(1)
Delete at beginning
O(n)
Delete at middle
O(n)
Update by index
O(1)
Traverse
O(n)

Examples

Example 1: Counting shifts

Array of 1000 elements. Insert at index 0 (the beginning):

  • Shift all 1000 elements one slot right
  • Write the new value
  • Total: 1001 steps

Insert at index 999 (near the end):

  • Shift only 1 element
  • Write the new value
  • Total: 2 steps

Both are technically O(n) — but in practice, inserting near the end is much faster.

Example 2: Search then delete

A common pattern: "find and remove the value 30 from [10, 20, 30, 40, 50]"

pseudocode
1
2
3
4
5
6
7
// Step 1: Search for 30
index = linearSearch(array, 30) // checks until it finds 30 at index 2
// Step 2: Delete at index 2
deleteAt(array, 2) // shifts 40 and 50 left
// Result: [10, 20, 40, 50]

Common Mistakes

  1. Shifting in the wrong direction during insert. When inserting, always shift from the end toward the insertion point. If you start from the insertion point and go right, you'll overwrite elements before you've copied them.
  1. Forgetting to update the size. After every insert or delete, the number of valid elements changes. If you don't track this, you'll either read garbage values or skip valid ones.
  1. Assuming insert is always O(1). Adding to the end is O(1). Adding anywhere else is O(n) because of shifting. Don't forget this.
  1. Not handling edge cases. What if the array is empty and you delete? What if it's full and you insert? What if you insert at position 0? Test these cases explicitly.

Best Practices

  • If you mostly read by index, arrays are great — O(1) access is as fast as it gets.
  • If you frequently add or remove elements from the middle, arrays are a poor fit. A different data structure (like a linked list) avoids the shifting cost.
  • When you need to delete an element and you don't care about order, swap it with the last element and delete from the end. This turns an O(n) delete into O(1).
  • Always track both the array's capacity (total slots) and its current size (elements stored). They are not the same thing.