1. What It Is and Why It Exists
An array is a contiguous block of memory holding elements of the same type, addressed by an integer index. Because the elements are contiguous and equal-sized, the address of element i is computed in one arithmetic step (base + i * size), which is why random access is O(1). That single property — instant indexed access — is what makes arrays the default container for almost every algorithm: , , prefix sums, sorting, and all rely on cheap positional access. Without it you would be back to linked structures where reaching element i costs O(i).
A string is logically an array of characters with one twist that dominates interview answers: in Python, strings are immutable. Any "modification" (s + c, s.replace(...)) builds a new string in O(n). The classic trap is concatenating in a loop, which is O(n^2); the fix is to collect characters in a list and "".join(...) once at the end. Python's list is a dynamic array of fixed-size references, so its elements can refer to objects of different types. It resizes automatically with amortized-O(1) append: a long sequence of appends costs O(1) per append overall even though an individual resize costs O(n). The examples here use list as the array representation.
Compute an address directly
Equal-sized contiguous elements let an array compute any address with base + i * size, giving constant-time indexed access.
What is the address at index five?
Text version
- What is the address at index five?
- Equal-sized, contiguous slots let us calculate an address from the index.
- Five times four puts index five twenty bytes past the base.
- Adding the base gives address one thousand twenty.
- The same formula works for any index, so access takes constant time.
- A linked list walks node by node, taking time proportional to the index.
A backend service builds a response string by repeatedly concatenating individual characters inside a loop: result = ""; for char in data: result += char. For an input of 10,000 characters, what is the time complexity of this approach, and what is the recommended fix in Python?
2. Operations and Complexity
| Operation | Average | Worst | Notes |
|---|---|---|---|
Index read/write a[i] | O(1) | O(1) | The defining strength |
Append a.append(x) | O(1) amortized | O(n) | Occasional resize is spread across many appends |
Pop end a.pop() | O(1) | O(1) | — |
Insert/delete at index i | O(n) | O(n) | Shifts all elements after i |
pop(0) / insert(0, x) | O(n) | O(n) | Front ops shift everything — use deque |
Search (unsorted) x in a | O(n) | O(n) | Linear scan |
| Search (sorted) | O(log n) | O(log n) | Binary search |
Slice a[i:j] | O(j-i) | O(n) | Copies the slice |
len(a) | O(1) | O(1) | Stored, not counted |
Space is O(n). The operations interviewers actually probe: insert/delete in the middle is O(n) (the shift) and front operations are O(n) — knowing to switch to collections.deque for -like access is a frequent signal. For strings, the probe is "you concatenated in a loop — what's the complexity?" (O(n^2)).
Compare operation growth rates
Indexing is constant time, sorted search is logarithmic, shifting is linear, and repeated string concatenation can be quadratic.
Index reads and writes take constant time because the address is computed directly.
Text version
- Index reads and writes take constant time because the address is computed directly.
- Binary search halves the sorted range each time, giving logarithmic growth.
- Middle and front insertions shift elements, so they take linear time.
- Copying the growing prefix for each appended character gives quadratic time.
- As n grows to sixty-four, the curves reach one, six, sixty-four, and four thousand ninety-six.
- The quadratic curve grows fastest here, so build strings with a list and join once.
A backend service processes a high-throughput message queue by repeatedly calling list.pop(0) to dequeue messages from the front of a Python list. As the list grows to millions of entries, this operation becomes a bottleneck. What is the time complexity of pop(0) on a Python list, and what is the recommended fix?
3. Implementation
The wrapper below demonstrates O(1) indexing and the O(n) shift using a fixed-capacity Python list. Use a positive capacity, insert only at 0 <= i <= current length, and delete only at 0 <= i < current length; these two methods assume valid indices rather than checking them. This is a teaching example, not a full replacement for list.
class Array:
def __init__(self, capacity):
self._data = [None] * capacity # contiguous slots
self._n = 0
self._cap = capacity
def get(self, i): # O(1): direct address arithmetic
if not 0 <= i < self._n:
raise IndexError
return self._data[i]
def set(self, i, x): # O(1)
if not 0 <= i < self._n:
raise IndexError
self._data[i] = x
def insert(self, i, x): # O(n): shift right to make a hole
if self._n == self._cap:
raise OverflowError
for j in range(self._n, i, -1): # this shift IS the O(n) cost
self._data[j] = self._data[j - 1]
self._data[i] = x
self._n += 1
def delete(self, i): # O(n): shift left to fill the gap
for j in range(i, self._n - 1):
self._data[j] = self._data[j + 1]
self._n -= 1In a real interview, use Python's built-in list — it is already a dynamic array with amortized-O(1) append. For string building, use a list + "".join(...), never += in a loop. Reach for collections.deque the moment you need O(1) operations at the front.
Shift right before inserting
Insertion copies elements rightward from the end before writing the new value; these shifts account for its linear worst-case cost.
We insert nine at index two, and n marks the first free slot, index five.
Text version
- We insert nine at index two, and n marks the first free slot, index five.
- The capacity check passes because five values leave room in eight slots.
- Copy seven into index five first, preserving it before the next write.
- Copy one into index four before index three gets overwritten.
- Copy five into index three before nine overwrites index two.
- Write nine at index two after the shifts finish.
- The length becomes six after three shifts, which cause the linear insertion cost.
A backend service maintains an ordered list of active user sessions stored in an array. A new session must be inserted at position 0 (the front) so the list stays sorted by arrival time. The array currently holds 10,000 sessions. What is the time complexity of this insertion, and why?
4. When to Reach for It
- You need indexed/random access or the input is already a sequence you scan by position.
- The problem screams , , or — all require
O(1)positional access. - You need a sorted sequence for (
O(log n)lookups). - You are building a string incrementally → array of chars +
join. - You need a (append/pop at the end are amortized
O(1)) — alistis the natural .
A backend engineer needs to build a feature that finds the maximum sum of any contiguous subarray of length k within a stream of API response times. Which data structure choice best supports this requirement, and why?
5. Common Pitfalls
- String concatenation in a loop:
s += cfornchars isO(n^2)(immutable rebuild each time). Fix:"".join(list_of_chars). list.pop(0)/list.insert(0, x)in a loop: each isO(n), so the loop isO(n^2). Fix:collections.deque(O(1)both ends), or reverse the logic to work from the back.- Mutating a list while iterating it: skips elements or raises; iterate a copy or build a new list.
[[0]*n]*maliasing: all rows are the same list object; mutating one mutates all. Use[[0]*n for _ in range(m)].- Off-by-one on bounds:
range(n)vsrange(n-1), inclusive vs exclusive slice ends — the most common array bug interviewers watch for. - Forgetting slices copy:
a[1:]inside a loop is a hiddenO(n)per call →O(n^2).
A backend developer writes the following Python code to build a response string from a list of 10,000 log tokens:
result = ""
for token in log_tokens:
result += token
What is the time complexity of this operation, and what is the recommended fix?
6. Interview Cheat Sheet
- "Array gives
O(1)indexed access because elements are contiguous; the cost isO(n)insert/delete in the middle due to shifting." - "Python
listis a dynamic array:append/popat the end are amortizedO(1);insert(0)/pop(0)areO(n)— I'd usedequefor front operations." - "Python strings are immutable, so I build with a list and
''.jointo avoid anO(n^2)concatenation." - "Allocating extra space is
O(n); in-place rearrangement keeps itO(1)auxiliary."
Follow-ups:
- "Array vs ?" — Array:
O(1)access,O(n)middle insert, cache-friendly. :O(n)access,O(1)insert/delete at a known node. Interviews almost always want an array unless the problem needsO(1)splicing. - "Why is your string building
O(n^2)?" — Immutable strings: each+=copies the whole prefix. Use a list and join →O(n). - "How do you delete from the middle in
O(1)?" — You can't preserve order inO(1); if order doesn't matter, swap the target with the last element andpop()(O(1)).