A complete deep-dive into every array operation: traversal, insertion, deletion, searching, updating, sorting, merging, and rotating — with Python and Java implementations, time and space complexity for each, and important edge cases to handle.
Introduction
Knowing that an array exists and knowing how to use it effectively are two different things. This lesson covers every fundamental array operation in depth: what it does, how it works internally, how to implement it correctly, what its time and space complexity is, and what edge cases you must handle.
These operations are the building blocks of almost every algorithm involving arrays. Master them here and they become automatic reflexes in problem-solving.
2. Access and Update
Definition: Reading or writing the value at a specific index position.
This is the defining superpower of arrays — direct O(1) access using the address formula.
arr = [10, 25, 8, 42, 15]
# Read (access) — O(1)
value = arr[2] # 8
first = arr[0] # 10
last = arr[-1] # 15 (Python shorthand for arr[len-1])
last = arr[len(arr)-1] # Same, explicit form
# Write (update) — O(1)
arr[2] = 99
print(arr) # [10, 25, 99, 42, 15]
arr[-1] = 0
print(arr) # [10, 25, 99, 42, 0]
Java and C equivalents
// Java
int[] arr = {10, 25, 8, 42, 15};
int value = arr[2]; // 8 — O(1)
arr[2] = 99; // O(1)
// C
int arr[] = {10, 25, 8, 42, 15};
int value = arr[2]; // 8 — O(1)
arr[2] = 99; // O(1)
Complexity: Time O(1), Space O(1)
Edge cases:
- Index out of bounds: accessing
arr[n] or arr[-n-1] raises IndexError in Python, ArrayIndexOutOfBoundsException in Java - Always validate:
0 <= index < len(arr) before accessing
3. Insertion
Definition: Adding a new element to the array at a specified position.
Insertion complexity depends entirely on *where* you insert.
Insert at End — O(1) Amortized
arr = [10, 25, 8, 42]
arr.append(15)
print(arr) # [10, 25, 8, 42, 15]
# Append multiple elements
for x in [100, 200, 300]:
arr.append(x)
print(arr) # [10, 25, 8, 42, 15, 100, 200, 300]
Insert at Beginning — O(n)
arr = [10, 25, 8, 42, 15]
arr.insert(0, 5) # Shift ALL elements right, then place 5 at index 0
print(arr) # [5, 10, 25, 8, 42, 15]
What happens internally:
| Before | [10, 25, 8, 42, 15] |
| Shift 15 | idx5: [10, 25, 8, 42, _, 15] |
| Shift 42 | idx4: [10, 25, 8, _, 42, 15] |
| Shift 8 | idx3: [10, 25, _, 8, 42, 15] |
| Shift 25 | idx2: [10, _, 25, 8, 42, 15] |
| Shift 10 | idx1: [_, 10, 25, 8, 42, 15] |
| Place 5 | idx0: [5, 10, 25, 8, 42, 15] |
| 5 shifts for 5 elements | O(n) |
Insert at Arbitrary Position — O(n)
def insert_at_position(arr, pos, value):
"""
Insert value at position pos.
Valid positions: 0 to len(arr) (inclusive).
"""
if pos < 0 or pos > len(arr):
raise IndexError(f"Position {pos} out of range for array of size {len(arr)}")
arr.insert(pos, value)
arr = [10, 25, 8, 42, 15]
insert_at_position(arr, 2, 99)
print(arr) # [10, 25, 99, 8, 42, 15]
Insert into Sorted Array — O(n)
def insert_into_sorted(arr, value):
"""
Insert value while maintaining sorted order.
Uses binary search to find position: O(log n)
Then shifts elements: O(n)
Total: O(n)
"""
left, right = 0, len(arr)
# Binary search for insertion point
while left < right:
mid = (left + right) // 2
if arr[mid] < value:
left = mid + 1
else:
right = mid
arr.insert(left, value)
arr = [2, 5, 8, 12, 18, 25]
insert_into_sorted(arr, 10)
print(arr) # [2, 5, 8, 10, 12, 18, 25]
insert_into_sorted(arr, 1)
print(arr) # [1, 2, 5, 8, 10, 12, 18, 25]
Complexity Summary for Insertion:
| Position | Time | Notes |
|---|
| End (append) | O(1) amortized | Best case |
| Beginning | O(n) | Shifts all elements |
| Position i | O(n-i) | Shifts elements from i to end |
| Sorted array | O(n) | O(log n) to find, O(n) to shift |
4. Deletion
Definition: Removing an element from the array at a specified position or by value.
Like insertion, deletion complexity depends on position.
Delete from End — O(1)
arr = [10, 25, 8, 42, 15]
removed = arr.pop() # Removes and returns last element
print(removed) # 15
print(arr) # [10, 25, 8, 42]
Delete from Beginning — O(n)
arr = [10, 25, 8, 42, 15]
removed = arr.pop(0) # Shifts ALL remaining elements left
print(removed) # 10
print(arr) # [25, 8, 42, 15]
What happens internally:
| Before | [10, 25, 8, 42, 15] |
| Shift 25 | idx0: [25, 8, 42, 15, _] |
| Shift 8 | idx1: now at idx1 (already there since we shifted 25) |
| After | [25, 8, 42, 15] (size decreases by 1) |
| 4 shifts for 4 remaining elements | O(n) |
Delete from Arbitrary Position — O(n)
arr = [10, 25, 8, 42, 15]
removed = arr.pop(2) # Remove element at index 2
print(removed) # 8
print(arr) # [10, 25, 42, 15]
# Delete by value (first occurrence)
arr = [10, 25, 8, 42, 8, 15]
arr.remove(8) # Finds first 8, removes it → O(n) search + O(n) shift
print(arr) # [10, 25, 42, 8, 15]
Delete All Occurrences of a Value
def delete_all_occurrences(arr, value):
"""
Remove all elements equal to value.
Two-pointer approach: O(n) time, O(1) space.
"""
write_pos = 0 # Position where next kept element should go
for read_pos in range(len(arr)):
if arr[read_pos] != value:
arr[write_pos] = arr[read_pos]
write_pos += 1
# Truncate array to write_pos length
del arr[write_pos:]
return arr
arr = [1, 2, 3, 2, 4, 2, 5]
delete_all_occurrences(arr, 2)
print(arr) # [1, 3, 4, 5]
Complexity Summary for Deletion:
| Position | Time | Notes |
|---|
| End (pop) | O(1) | Best case |
| Beginning (pop(0)) | O(n) | Shifts all remaining |
| Position i (pop(i)) | O(n-i) | Shifts elements after i |
| By value (remove) | O(n) | O(n) search + O(n) shift |
| All occurrences | O(n) | Two-pointer method |
5. Searching
Definition: Finding whether a value exists in the array, and if so, at which index.
# Linear Search — works on any array — O(n)
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
arr = [10, 25, 8, 42, 15]
print(linear_search(arr, 42)) # 3
print(linear_search(arr, 99)) # -1
# Binary Search — requires sorted array — O(log n)
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
sorted_arr = [5, 12, 18, 30, 47, 55, 63, 77]
print(binary_search(sorted_arr, 47)) # 4
print(binary_search(sorted_arr, 20)) # -1
Find all occurrences:
def find_all_indices(arr, target):
return [i for i, x in enumerate(arr) if x == target]
arr = [1, 3, 5, 3, 7, 3, 9]
print(find_all_indices(arr, 3)) # [1, 3, 5]
Complexity:
| Method | Time | Requires |
|---|
| Linear search | O(n) | Nothing |
| Binary search | O(log n) | Sorted array |
Python in operator | O(n) | Nothing |
Python index() method | O(n) | Nothing |
6. Sorting
Definition: Rearranging array elements into a specified order (ascending or descending).
arr = [64, 25, 12, 22, 11]
# Python built-in — O(n log n), stable (TimSort)
arr.sort() # In-place, modifies arr
print(arr) # [11, 12, 22, 25, 64]
arr.sort(reverse=True) # Descending
print(arr) # [64, 25, 22, 12, 11]
# sorted() — returns new array, original unchanged
arr = [64, 25, 12, 22, 11]
sorted_arr = sorted(arr)
print(sorted_arr) # [11, 12, 22, 25, 64]
print(arr) # [64, 25, 12, 22, 11] — unchanged
# Sort with custom key
words = ["banana", "apple", "cherry", "date"]
words.sort(key=len) # Sort by word length
print(words) # ['date', 'apple', 'banana', 'cherry']
# Sort list of tuples by second element
pairs = [(1, 3), (4, 1), (2, 2), (3, 4)]
pairs.sort(key=lambda x: x[1])
print(pairs) # [(4, 1), (2, 2), (1, 3), (3, 4)]
Complexity: O(n log n) time, O(log n) space (for TimSort's internal merge operations)
7. Merging Two Arrays
Definition: Combining two arrays into one. If both are sorted, produce a merged sorted array.
# Simple concatenation — O(m + n)
arr1 = [1, 3, 5, 7]
arr2 = [2, 4, 6, 8]
merged = arr1 + arr2
print(merged) # [1, 3, 5, 7, 2, 4, 6, 8]
# Merge two sorted arrays into one sorted array — O(m + n)
def merge_sorted_arrays(arr1, arr2):
result = []
i = j = 0
while i < len(arr1) and j < len(arr2):
if arr1[i] <= arr2[j]:
result.append(arr1[i])
i += 1
else:
result.append(arr2[j])
j += 1
# Append remaining elements (at most one of these runs)
result.extend(arr1[i:])
result.extend(arr2[j:])
return result
print(merge_sorted_arrays([1, 3, 5, 7], [2, 4, 6, 8]))
# [1, 2, 3, 4, 5, 6, 7, 8]
print(merge_sorted_arrays([1, 5, 9], [2, 3, 4, 6, 7, 8]))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Complexity: Time O(m + n), Space O(m + n) for the result array
8. Reversing an Array
Definition: Reversing the order of all elements.
arr = [1, 2, 3, 4, 5]
# Method 1: In-place with two pointers — O(n) time, O(1) space
def reverse_in_place(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
return arr
arr = [1, 2, 3, 4, 5]
reverse_in_place(arr)
print(arr) # [5, 4, 3, 2, 1]
# Method 2: Python built-in (in-place) — O(n)
arr = [1, 2, 3, 4, 5]
arr.reverse()
print(arr) # [5, 4, 3, 2, 1]
# Method 3: Slice (creates new array) — O(n) time, O(n) space
arr = [1, 2, 3, 4, 5]
reversed_arr = arr[::-1]
print(reversed_arr) # [5, 4, 3, 2, 1]
print(arr) # [1, 2, 3, 4, 5] — original unchanged
Two-pointer visualization:
| L R | swap arr[0] and arr[4] → [5, 2, 3, 4, 1] |
| L R | swap arr[1] and arr[3] → [5, 4, 3, 2, 1] |
| L=R | L == R, stop (odd length) or L > R (even length) |
Complexity: Time O(n), Space O(1) (in-place), or O(n) (copy)
9. Finding Minimum and Maximum
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
# Python built-in — O(n) each
print(min(arr)) # 1
print(max(arr)) # 9
# Manual — O(n), single pass
def find_min_max(arr):
if not arr:
return None, None
minimum = maximum = arr[0]
for x in arr[1:]:
if x < minimum:
minimum = x
if x > maximum:
maximum = x
return minimum, maximum
print(find_min_max(arr)) # (1, 9)
# Find both in single pass (at most 3n/2 comparisons)
def find_min_max_optimal(arr):
"""Processes pairs, reducing total comparisons from 2n to 3n/2."""
if not arr:
return None, None
if len(arr) == 1:
return arr[0], arr[0]
minimum, maximum = min(arr[0], arr[1]), max(arr[0], arr[1])
for i in range(2, len(arr) - 1, 2):
small = min(arr[i], arr[i + 1])
large = max(arr[i], arr[i + 1])
minimum = min(minimum, small)
maximum = max(maximum, large)
if len(arr) % 2 == 1: # Odd length — handle last element
minimum = min(minimum, arr[-1])
maximum = max(maximum, arr[-1])
return minimum, maximum
Complexity: Time O(n), Space O(1)
10. Array Rotation
Definition: Shifting all elements left or right by k positions.
# Left rotation by k positions — O(n) time, O(1) space
# Using reversal algorithm (most efficient)
def rotate_left(arr, k):
n = len(arr)
k = k % n # Handle k > n
reverse(arr, 0, k - 1) # Reverse first k elements
reverse(arr, k, n - 1) # Reverse remaining elements
reverse(arr, 0, n - 1) # Reverse entire array
def reverse(arr, start, end):
while start < end:
arr[start], arr[end] = arr[end], arr[start]
start += 1
end -= 1
arr = [1, 2, 3, 4, 5, 6, 7]
rotate_left(arr, 3)
print(arr) # [4, 5, 6, 7, 1, 2, 3]
# Right rotation — O(n), O(1)
def rotate_right(arr, k):
n = len(arr)
k = k % n
reverse(arr, 0, n - k - 1) # Reverse first n-k elements
reverse(arr, n - k, n - 1) # Reverse last k elements
reverse(arr, 0, n - 1) # Reverse entire array
arr = [1, 2, 3, 4, 5, 6, 7]
rotate_right(arr, 3)
print(arr) # [5, 6, 7, 1, 2, 3, 4]
Reversal algorithm visualization for left rotate by 3:
| Original | [1, 2, 3, 4, 5, 6, 7] |
| Step 1 — reverse first 3 | [3, 2, 1, 4, 5, 6, 7] |
| Step 2 — reverse last 4 | [3, 2, 1, 7, 6, 5, 4] |
| Step 3 — reverse all | [4, 5, 6, 7, 1, 2, 3] |
Complexity: Time O(n), Space O(1)
Complete Operations Reference
| Operation | Time | Space | Notes |
|---|
| Access arr[i] | O(1) | O(1) | Address formula |
| Update arr[i] | O(1) | O(1) | |
| Traverse all | O(n) | O(1) | |
| Search (linear) | O(n) | O(1) | |
| Search (binary) | O(log n) | O(1) | Sorted only |
| Append | O(1)* | O(1) | *Amortized |
| Insert at front | O(n) | O(1) | |
| Insert at pos i | O(n-i) | O(1) | |
| Delete from end | O(1) | O(1) | |
| Delete from front | O(n) | O(1) | |
| Delete at pos i | O(n-i) | O(1) | |
| Sort | O(n log n) | O(log n) | |
| Reverse | O(n) | O(1) | |
| Merge sorted | O(m+n) | O(m+n) | |
| Rotate | O(n) | O(1) | Reversal method |
| Min/Max | O(n) | O(1) | |
Summary
Every array operation has a well-defined complexity determined by whether it requires shifting elements. The key insight: O(1) operations touch a fixed number of elements; O(n) operations touch a number of elements proportional to the array size.
- Never worry about: Access, update, append — always O(1)
- Always consider alternatives for: Frequent insertions or deletions at the front or middle — a linked list or deque may be more appropriate
These operations form the vocabulary of array manipulation. In the next lessons, you will see how to combine them cleverly to solve complex problems efficiently.
*Next Lesson: Array Rotation — Deep dive into rotation algorithms, circular arrays, and rotation-based problem patterns.*