AiTechWorlds
AiTechWorlds
Imagine a large apartment building with 500 numbered mailboxes lining the lobby wall. Each box has a label — Box 1, Box 2, Box 3 — all the way to Box 500. They sit in a straight, unbroken row.
If you need to deliver a package to Box 347, you do not start at Box 1 and count forward. You walk directly to that section of the wall. You know exactly where it is because the boxes are evenly spaced and numbered sequentially.
That is an array. Items stored in a continuous, ordered row, each with a numbered address, each accessible instantly because the location can be calculated mathematically.
This simple concept — contiguous, indexed storage — underlies nearly every other data structure in computing. Understanding arrays deeply means understanding why they are fast, when they break down, and what every other structure was designed to work around.
In classic computer science (and languages like C, Java, Go), an array is:
This last property is the source of all of an array's power.
When your program creates an array, the operating system hands it a block of memory starting at some address — let's say memory address 1000. If each integer takes 4 bytes, here is what the layout looks like:
Index: [0] [1] [2] [3] [4]
Memory: 1000 1004 1008 1012 1016
Value: 12 45 7 99 23
To find element at index 3, the CPU calculates:
address = base_address + (index × element_size)
address = 1000 + (3 × 4) = 1012
One arithmetic operation. One memory read. Done. This is why array access is O(1) regardless of array size.
Python's list is not a traditional array. It is a dynamic array that:
[1, "hello", 3.14, True])For a true, type-homogeneous array in Python, use the built-in array module:
import array
# 'i' means signed integer, each element is exactly 4 bytes
int_array = array.array('i', [10, 20, 30, 40, 50])
print(int_array[2]) # Output: 30
print(type(int_array)) # Output: <class 'array.array'>
# Type enforcement — this will raise a TypeError:
# int_array.append(3.14)
For most Python work, list is used. For memory-critical or performance-critical numerical work, array or numpy arrays are preferred.
| Operation | Time Complexity | Why |
|---|---|---|
Access by index arr[i] | O(1) | Direct address calculation |
| Search (unsorted) | O(n) | Must check each element linearly |
| Search (sorted) | O(log n) | Binary search halves the space each step |
| Insert at end | O(1) amortized | Space is already available (in dynamic arrays) |
| Insert at beginning or middle | O(n) | Every element after must shift right |
| Delete from end | O(1) | Just decrease the size counter |
| Delete from beginning or middle | O(n) | Every element after must shift left |
The critical insight: position matters. Operations at the end are fast. Operations anywhere else require shifting.
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# O(1) access — direct index
print(fruits[0]) # Output: apple
print(fruits[-1]) # Output: elderberry (Python supports negative indexing)
print(fruits[2]) # Output: cherry
# O(n) linear search
def linear_search(arr, target):
for i, item in enumerate(arr):
if item == target:
return i
return -1
print(linear_search(fruits, "date")) # Output: 3
print(linear_search(fruits, "mango")) # Output: -1
numbers = [10, 20, 30, 40, 50]
# O(1) at end
numbers.append(60)
print(numbers) # Output: [10, 20, 30, 40, 50, 60]
# O(n) at middle — Python shifts elements 30, 40, 50, 60 right
numbers.insert(2, 25)
print(numbers) # Output: [10, 20, 25, 30, 40, 50, 60]
# O(n) deletion from middle — Python shifts elements left
numbers.pop(2)
print(numbers) # Output: [10, 20, 30, 40, 50, 60]
Given an array of integers and a target, find two numbers that add up to the target.
def two_sum(arr, target):
seen = {} # Hash map for O(1) lookup
for i, num in enumerate(arr):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
# Sample input
numbers = [2, 7, 11, 15]
target = 9
result = two_sum(numbers, target)
print(result) # Output: [0, 1] (arr[0] + arr[1] = 2 + 7 = 9)
Line-by-line explanation:
seen = {} — stores each number's index as we scancomplement = target - num — if we need 9 and current is 7, we need 2if complement in seen — have we seen the number we need? O(1) checkdef reverse_array(arr):
left = 0
right = len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left] # Swap
left += 1
right -= 1
return arr
# Sample input
data = [1, 2, 3, 4, 5]
print(reverse_array(data)) # Output: [5, 4, 3, 2, 1]
Why this is efficient: Two pointers move toward each other, each pair swapped once. Time: O(n). Space: O(1) — no extra array needed.
Move the last k elements to the front of the array.
def rotate_right(arr, k):
n = len(arr)
k = k % n # Handle k larger than array length
def reverse(start, end):
while start < end:
arr[start], arr[end] = arr[end], arr[start]
start += 1
end -= 1
reverse(0, n - 1) # Reverse entire array
reverse(0, k - 1) # Reverse first k elements
reverse(k, n - 1) # Reverse remaining elements
return arr
# Sample input
nums = [1, 2, 3, 4, 5, 6, 7]
k = 3
print(rotate_right(nums, k)) # Output: [5, 6, 7, 1, 2, 3, 4]
Step-by-step for [1,2,3,4,5,6,7] with k=3:
[7, 6, 5, 4, 3, 2, 1][5, 6, 7, 4, 3, 2, 1][5, 6, 7, 1, 2, 3, 4]Time: O(n). Space: O(1).
Arrays are the right choice when you need:
Arrays are the wrong choice when you need:
list is a dynamic array — flexible and general-purposearray.array provides true typed arrays with lower memory overheadThe next lesson dives deeper into how Python lists actually grow dynamically, how memory is managed under the hood, and why append() is so reliably fast even though the array must sometimes be resized.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises