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
  1. What is the address at index five?
  2. Equal-sized, contiguous slots let us calculate an address from the index.
  3. Five times four puts index five twenty bytes past the base.
  4. Adding the base gives address one thousand twenty.
  5. The same formula works for any index, so access takes constant time.
  6. A linked list walks node by node, taking time proportional to the index.
Quick check

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?

Choose one answer

2. Operations and Complexity

OperationAverageWorstNotes
Index read/write a[i]O(1)O(1)The defining strength
Append a.append(x)O(1) amortizedO(n)Occasional resize is spread across many appends
Pop end a.pop()O(1)O(1)—
Insert/delete at index iO(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 aO(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
  1. Index reads and writes take constant time because the address is computed directly.
  2. Binary search halves the sorted range each time, giving logarithmic growth.
  3. Middle and front insertions shift elements, so they take linear time.
  4. Copying the growing prefix for each appended character gives quadratic time.
  5. As n grows to sixty-four, the curves reach one, six, sixty-four, and four thousand ninety-six.
  6. The quadratic curve grows fastest here, so build strings with a list and join once.
Quick check

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?

Choose one answer

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 -= 1

In 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
  1. We insert nine at index two, and n marks the first free slot, index five.
  2. The capacity check passes because five values leave room in eight slots.
  3. Copy seven into index five first, preserving it before the next write.
  4. Copy one into index four before index three gets overwritten.
  5. Copy five into index three before nine overwrites index two.
  6. Write nine at index two after the shifts finish.
  7. The length becomes six after three shifts, which cause the linear insertion cost.
Quick check

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?

Choose one answer

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)) — a list is the natural .
Quick check

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?

Choose one answer

5. Common Pitfalls

  • String concatenation in a loop: s += c for n chars is O(n^2) (immutable rebuild each time). Fix: "".join(list_of_chars).
  • list.pop(0) / list.insert(0, x) in a loop: each is O(n), so the loop is O(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]*m aliasing: 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) vs range(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 hidden O(n) per call → O(n^2).
Quick check

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?

Choose one answer

6. Interview Cheat Sheet

  • "Array gives O(1) indexed access because elements are contiguous; the cost is O(n) insert/delete in the middle due to shifting."
  • "Python list is a dynamic array: append/pop at the end are amortized O(1); insert(0)/pop(0) are O(n) — I'd use deque for front operations."
  • "Python strings are immutable, so I build with a list and ''.join to avoid an O(n^2) concatenation."
  • "Allocating extra space is O(n); in-place rearrangement keeps it O(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 needs O(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 in O(1); if order doesn't matter, swap the target with the last element and pop() (O(1)).

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.