InterviewKata
Data structure 1 of 12

Arrays and lists

An array is a row of numbered boxes. Jumping to box i takes one step, however long the row is; squeezing a new box into the middle means shifting everything after it. Almost every interview problem starts with one.

A 3-minute conversation: Sam asks, Priya explains
0:00 / 3:01

1. What it is

A row of school lockers, numbered from 0. To open locker 7 you walk straight to it: you don't count lockers 0 to 6. But if a new student needs a locker between 3 and 4, everyone from 4 onwards has to move one locker along.

An array stores values side by side, each in a numbered slot called an index. The first index is 0, so a row of n values has indices 0 to n − 1. A list in Python (an Array in TypeScript, an ArrayList in Java) is an array that grows by itself when you add to the end.

2. How it sits in memory

The values sit in one unbroken block of memory. If the block starts at address 1000 and each value takes 8 bytes, value i lives at 1000 + 8 × i. One multiplication and one addition find any box, which is why indexing costs O(1).

A list keeps some spare boxes at the end. When they run out it asks for a block about twice as big and copies everything across. Copying is slow, but it happens so rarely that adding to the end still costs O(1) on average (called amortised).

3. Operations and what they cost

OperationPythonCostWhy
Read or write at index inums[i], nums[i] = xO(1)The address is computed directly.
Lengthlen(nums)O(1)The list keeps a count.
Add or remove at the endappend(x), pop()O(1) amortisedNo shifting; an occasional resize.
Insert or remove at index iinsert(i, x), pop(i)O(n)Everything after i shifts by one.
Search for a valuex in nums, nums.index(x)O(n)Checks the boxes one by one.
Copy a slicenums[a:b]O(b − a)Makes a new list.
Sortnums.sort()O(n log n)Comparison sorting.
Rule of thumbWork at the end of a list, never at the front. If you need to add or remove at both ends, use a deque (structure 07).

4. In Python, TypeScript and Java

The same everyday operations in each language. Switch the language with the tabs above any code block.

nums = [7, 3, 9]          # a list
    nums.append(4)            # [7, 3, 9, 4]
    last = nums.pop()         # 4; nums is [7, 3, 9] again
    nums[1] = 8               # [7, 8, 9]
    size = len(nums)          # 3
    first_two = nums[:2]      # [7, 8], a new list
    grid = [[0] * 3 for _ in range(2)]   # 2 rows, 3 columns, each row its own list
    for i, x in enumerate(nums):         # index and value together
        print(i, x)

5. Core operations, step by step

Five things you will do with arrays all the time. Each player runs the code line by line: press Play to hear it explained, or step with the arrow keys.

A · Walk every item (find the largest)

What it doesVisits each index once, keeping the best value so far.
Picture itWalking along the lockers with a sticky note that holds the biggest number seen so far.
CostO(n) time, O(1) extra space.

The code. Start with the first value, then compare every other value against it.

def largest(nums):
    best = nums[0]
    for i in range(1, len(nums)):
        if nums[i] > best:
            best = nums[i]
    return best

Line by line.

LineWhat it doesWhy
best = nums[0]Start with the first value.Starting at 0 would be wrong for an all-negative array.
for i in range(1, len(nums))Visit indices 1 to n − 1.Index 0 is already in best.
if nums[i] > bestCompare with the best so far.One comparison per item: O(n) in total.

Watch it run. Find the largest value in [7, 3, 9, 4].

Watch out for:

  • Starting best at 0. It fails when every value is negative. Start from nums[0].
  • Looping to len(nums) inclusive. The last index is len(nums) − 1.

B · Append, and how a list grows

What it doesAdds a value at the end; doubles the storage when it is full.
Picture itA shelf with a few empty spaces at the end. When it is full you buy a shelf twice as long and move every book once.
CostO(1) amortised: an occasional O(n) copy, spread over many cheap appends.

The code. A tiny version of what Python's list, TypeScript's array and Java's ArrayList do for you.

class DynamicArray:
    def __init__(self):
        self.slots = [None]
        self.size = 0

    def append(self, x):
        if self.size == len(self.slots):
            bigger = [None] * (2 * len(self.slots))
            for i in range(self.size):
                bigger[i] = self.slots[i]
            self.slots = bigger
        self.slots[self.size] = x
        self.size += 1

Line by line.

LineWhat it doesWhy
if self.size == len(self.slots)Is every slot used?Only then do we pay for a copy.
bigger = [None] * (2 * len(self.slots))A block twice as big.Doubling keeps copies rare: 1, 2, 4, 8 … Growing by one would copy on every append (O(n²) in total).
self.slots[self.size] = xWrite into the first free slot.size is also the index of the first free slot.

Watch it run. Append 5, 8, 2, 6 and 1 to an empty list that starts with one slot.

Watch out for:

  • Thinking every append is O(1). Most are; the occasional resize is O(n). On average it is still O(1).
  • Growing by a fixed amount. Adding 10 slots at a time makes n appends cost O(n²). Grow by a factor.

C · Insert in the middle (shift right)

What it doesPuts a value at index i, moving everything from i onwards one place right.
Picture itSomeone joins the middle of a queue: everyone behind them takes one step back.
CostO(n): up to n values move.

The code. Make room at the end, then shift from the back so nothing is overwritten.

def insert_at(nums, i, x):
    nums.append(None)
    for j in range(len(nums) - 1, i, -1):
        nums[j] = nums[j - 1]
    nums[i] = x
    return nums

Line by line.

LineWhat it doesWhy
nums.append(None)One more box at the end.The row must grow by one.
for j in range(len(nums) - 1, i, -1)Walk from the back towards i.Shifting from the front would copy the same value along the whole row.
nums[j] = nums[j - 1]Move the left neighbour right.Each move frees the box to its left.
nums[i] = xDrop the new value in.Box i is free now.

Watch it run. Insert 5 at index 1 of [7, 3, 9, 4].

Watch out for:

  • Shifting from the front. You overwrite the next value before moving it.
  • Inserting at the front inside a loop. n inserts at index 0 cost O(n²). Build the list in reverse and flip it once, or use a deque.

D · Delete from the middle (shift left)

What it doesRemoves the value at index i, moving everything after it one place left.
Picture itSomeone leaves the queue: everyone behind steps forward.
CostO(n).

The code. Shift from i towards the end, then drop the last box.

def remove_at(nums, i):
    for j in range(i, len(nums) - 1):
        nums[j] = nums[j + 1]
    nums.pop()
    return nums

Line by line.

LineWhat it doesWhy
for j in range(i, len(nums) - 1)From i up to the second-to-last box.The last box has no right neighbour to copy.
nums[j] = nums[j + 1]Pull the right neighbour left.Going left to right never overwrites a value we still need.
nums.pop()Remove the now-duplicate last box.O(1) at the end.

Watch it run. Remove index 1 from [7, 5, 3, 9, 4].

Watch out for:

  • Removing items while looping forwards over the same list. Indices shift under you and items get skipped. Build a new list, or loop backwards.
  • Using pop(0) as a queue. Every pop(0) shifts the whole list. Use a deque.

E · A 2-D grid: rows, columns and neighbours

What it doesReads grid[r][c] and lists the cells up, down, left and right of it that are inside the grid.
Picture itSeats in a cinema: row first, then seat number. Your neighbours are the seats in front, behind and either side.
CostO(1) per cell; walking every cell is O(rows × cols).

The code. A grid is a list of rows. Check the edges before stepping off them.

def neighbours(grid, r, c):
    rows, cols = len(grid), len(grid[0])
    found = []
    for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
        nr, nc = r + dr, c + dc
        if 0 <= nr < rows and 0 <= nc < cols:
            found.append(grid[nr][nc])
    return found

Line by line.

LineWhat it doesWhy
rows, cols = len(grid), len(grid[0])The grid's height and width.Rows first, then columns, everywhere.
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]Up, down, left, right.A list of directions beats four copies of the same if.
if 0 <= nr < rows and 0 <= nc < colsIs the neighbour inside the grid?Python would quietly read grid[-1] (the last row) without this check.

Watch it run. Find the neighbours of the cell at row 0, column 1 in a 2 × 3 grid.

Watch out for:

  • [[0] * cols] * rows. Every row is the same list: changing one changes all. Use a comprehension.
  • Swapping r and c. grid[r][c]: row first. Name them r and c, never x and y.

6. How to remember it

Memory hook: "Numbered lockers"Walk straight to any locker by its number. Add or remove at the end of the corridor freely; squeeze into the middle and everyone shuffles along.

Picture

A corridor of lockers numbered 0, 1, 2 … with a few empty ones at the far end.

Chant

"Index is instant, the end is cheap, the middle shifts."

Key rule

The last index is len − 1. Every loop bound should make you think about it.

7. Problems solved with it

Three easy LeetCode problems that need nothing but the operations above.

LC 1480 · Running Sum of 1d Array

The problemReturn an array where element i is the sum of nums[0] through nums[i].
What it doesWalks once, adding the previous total into the current box.
CostO(n) time, O(1) extra space (in place).

The code. Each box becomes itself plus the box before it.

def running_sum(nums):
    for i in range(1, len(nums)):
        nums[i] += nums[i - 1]
    return nums

Line by line.

LineWhat it doesWhy
for i in range(1, len(nums))Start at index 1.Index 0's running sum is itself.
nums[i] += nums[i - 1]Add the total so far.nums[i − 1] already holds the sum of everything before it.

Watch it run. [1, 2, 3, 4].

Watch out for:

  • Starting at 0. nums[-1] is the last element in Python: index 0 would add the wrong value.

LC 27 · Remove Element

The problemRemove every occurrence of val from nums in place and return k, the number of values left. The first k boxes must hold them.
What it doesCopies each value worth keeping to the next free box at the front.
CostO(n) time, O(1) extra space.

The code. write is the next box to fill; read visits every box.

def remove_element(nums, val):
    write = 0
    for read in range(len(nums)):
        if nums[read] != val:
            nums[write] = nums[read]
            write += 1
    return write

Line by line.

LineWhat it doesWhy
write = 0The next box to fill.Everything left of write is a kept value.
if nums[read] != valIs this value a keeper?Values equal to val are simply skipped.
nums[write] = nums[read]Copy it forward.write never passes read, so nothing unread is overwritten.

Watch it run. Remove 3 from [3, 2, 2, 3, 4].

Watch out for:

  • Calling remove() in a loop. Each remove shifts the rest: O(n²). Overwrite instead.

LC 1672 · Richest Customer Wealth

The problemaccounts[i][j] is how much customer i has in bank j. Return the largest total any customer has.
What it doesSums each row, keeping the largest row total.
CostO(rows × cols) time, O(1) space.

The code. A grid walk: one loop over rows, one over the cells of a row.

def maximum_wealth(accounts):
    best = 0
    for row in accounts:
        total = 0
        for money in row:
            total += money
        best = max(best, total)
    return best

Line by line.

LineWhat it doesWhy
total = 0A fresh total for each customer.Resetting outside the row loop would add every customer together.
best = max(best, total)Keep the richest so far.Compare once per row, after the row is summed.

Watch it run. Three customers, two banks each.

Watch out for:

  • Resetting total in the wrong place. Reset it at the start of each row.

More practice, solved on the pattern pages

#ProblemLevelWhere
26Remove Duplicates from Sorted ArrayEasySolved step by step on 02 · Two Pointers
88Merge Sorted ArrayEasySolved step by step on 02 · Two Pointers
977Squares of a Sorted ArrayEasySolved step by step on 02 · Two Pointers
121Best Time to Buy and Sell StockEasySolved step by step on 03 · Sliding Window
2239Find Closest Number to ZeroEasySolved step by step on 21 · Array & String Basics
704Binary SearchEasySolved step by step on 09 · Modified Binary Search

8. Common mistakes

Off by oneA list of length n has indices 0 to n − 1. nums[len(nums)] is an error; range(len(nums)) already stops before n.
Changing a list while looping over itRemoving items during a forward loop skips the item after each removal. Loop over a copy, loop backwards, or build a new list.
Working at the frontinsert(0, x) and pop(0) shift every element: O(n) each, O(n²) in a loop. Use a deque, or build backwards and reverse once.
Shared rows in a grid[[0] * 3] * 2 makes two references to one row. Write [[0] * 3 for _ in range(2)].
Negative indices by accidentIn Python nums[-1] is the last item, not an error. A bug that computes index −1 fails silently; check bounds yourself.

9. Self-check quiz

Reading nums[500000] in a list of a million numbers costs…

The address is start + 500000 × size: one calculation, whatever the index.

What does nums.insert(0, x) cost, and why?

All n values move one place right to make room at the front.

Why is append O(1) on average even though it sometimes copies the whole list?

After a copy of n items, the next n appends are free. Spread out, each append pays a constant.

g = [[0] * 3] * 3, then g[0][0] = 1. What happens?

The outer * 3 repeats a reference to the same inner list.

10. Where it shows up next

Arrays are the starting point of most patterns. These pages build directly on what you just learned:

Next structure: 02 · Strings and characters, which are arrays of letters with one important twist.