An array stores elements in one contiguous block of memory, and a string is essentially an array of characters. That layout is what makes index access instant: the computer can jump straight to position without scanning anything before it.
The trade-off is rigidity. Reading by index costs , but inserting or deleting in the middle costs , because every later element has to shift. Most array and string problems come down to managing that trade-off.
How array indexing works
Arrays are zero-based in most languages: the first element lives at index and the last at index for an array of length . Access is constant time because the address of any element is a direct calculation:
No loop is needed, which is why arr[500000] is exactly as fast as arr[0].
For the array :
| Index | Element | Meaning |
|---|---|---|
| 0 | 8 | first element |
| 1 | 3 | second element |
| 3 | 1 | last element () |
| 4 | — | out of bounds |
Complexity of common operations
| Operation | Complexity | Why |
|---|---|---|
| Access by index | direct address calculation | |
| Search (unsorted) | may need to check every element | |
| Search (sorted) | binary search halves the range | |
| Append at end | amortized | occasional resize, usually free |
| Insert or delete in middle | every later element must shift |
The middle-insert cost is the one students underestimate. Deleting index from a million-element array touches all remaining elements, because each one slides left by a single slot.
Strings are arrays with one extra rule
In many languages — Python, Java, JavaScript — strings are immutable: you cannot change a character in place. Every "modification" actually builds a new string.
That makes one pattern dangerously slow. Concatenating inside a loop copies the whole string each time:
s = ""
for ch in data: # O(n^2) total
s += ch
Each += copies everything built so far, so the total work is
The fix is to collect pieces in a list and join once at the end ("".join(parts) in Python), which is .
Worked example: reverse an array in place
Put one pointer at each end, swap, and move inward:
def reverse(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
Trace on :
| Step | left | right | Array after swap |
|---|---|---|---|
| 1 | 0 | 4 | |
| 2 | 1 | 3 | |
| 3 | 2 | 2 | loop stops — pointers met |
Each element is touched once, so the reversal runs in time with extra space. This two-pointer pattern reappears constantly in array and string problems.
Worked example: check if two strings are anagrams
Two strings are anagrams if they use the same characters with the same counts. Count characters from one string, then subtract with the other:
def is_anagram(a, b):
if len(a) != len(b):
return False
counts = {}
for ch in a:
counts[ch] = counts.get(ch, 0) + 1
for ch in b:
counts[ch] = counts.get(ch, 0) - 1
if counts[ch] < 0:
return False
return True
For a = "listen" and b = "silent", every count returns to zero, so the function returns True. This runs in — better than sorting both strings first, which would cost .
Common mistakes
Off-by-one errors
The last valid index is , not . A loop written as while i <= len(arr) reads one element past the end and crashes — or worse, silently reads garbage in low-level languages.
Concatenating strings in a loop
As shown above, repeated += on an immutable string is . Collect parts in a list and join once, or use a string builder.
Modifying an array while iterating forward
Deleting elements during a forward loop shifts the remaining ones, so the loop skips the neighbor of every removed element. Iterate backwards or build a new array instead.
Confusing length with last index
len(arr) is the count of elements; len(arr) - 1 is the last position. Mixing the two is the single most common cause of index-out-of-range errors.
Where this takes you next
Arrays are the backbone of nearly everything else: hash tables keep their buckets in arrays, sorting algorithms permute arrays, and binary search depends on index access to jump to the middle of a range. Once you can predict the cost of an array operation before running it, the data structures built on top of arrays become far easier to reason about.
Frequently Asked Questions
- What is the difference between an array and a string?
- An array is a contiguous block of memory holding elements of one type, while a string is essentially an array of characters. The practical difference is that in many languages strings are immutable, so any change builds a new string, whereas array elements can usually be modified in place.
- Why is array indexing O(1)?
- Because elements sit next to each other in memory, the address of position i is computed directly from the base address plus i times the element size. No scanning is required, so accessing the millionth element takes the same time as accessing the first one.
- Why is inserting into the middle of an array slow?
- Arrays keep elements contiguous, so inserting at position i forces every element after it to shift one slot to the right. In the worst case that means moving nearly all n elements, which is why middle insertions and deletions cost O(n) even though index access is O(1).
- Why is string concatenation in a loop slow?
- Immutable strings cannot grow in place, so each concatenation copies everything built so far into a new string. Repeating that n times performs roughly n squared character copies in total. Collecting the pieces in a list and joining them once at the end reduces the work to linear time.
Need help with a problem?
Upload your question and get a verified, step-by-step solution in seconds.
Open GPAI Solver →