StudyHub

Algorithms · Linear structures

Arrays

Take [3, 8, 5] stored in slots 0, 1, 2. Reading a[1] jumps straight to 8. Inserting 7 at index 1 is different: 8 and 5 must each shift one slot right first, giving [3, 7, 8, 5]. That's an array: instant access by index, costly moves in the middle.

Why it matters: elements sit side by side in memory, so the address of a[i] is one multiplication away. Reading or writing any index is $O(1)$, but inserting or deleting in the middle shifts up to $n$ elements.

Prerequisites

  • Variables and memory addresses: every byte has a number.
  • A for loop that counts down as well as up.
  • Big-O notation.

The analogy

Everyday analogy

Think of a row of numbered lockers. To open locker 57 you walk straight to it; you never check lockers 1 to 56 first.

But to put a new item in locker 3 while keeping everything in order, every item from locker 3 onwards must move one locker to the right. Arrays work exactly like this row of lockers.

How it works

Start with a sequence of operations by hand

The array has capacity 5 and holds [3, 8, 5]. Do each operation and guess its cost before opening "Check".

Example: predict before you read

Step 1. Read a[1]. Predict: how many elements do you look at?

Check

Just one. The address is computed directly, so you get 8 in one step.

Step 2. Cover the next line and guess: to insert 7 at index 1, which elements move, and in which order?

Check

First 5 moves from slot 2 to 3, then 8 from slot 1 to 2. Moving from the end avoids overwriting. Then 7 goes into slot 1: [3, 7, 8, 5].

Step 3. Delete a[0], then append 9. Predict the array and which step was cheaper.

Check

Deleting shifts 7, 8, 5 one slot left: [7, 8, 5]. Appending 9 just writes slot 3: [7, 8, 5, 9]. Appending was cheaper.

Array operations on 3 8 5: read a[1], insert 7 at index 1 by shifting right, delete a[0] by shifting left, then append 9
What you just did by hand: blue boxes are elements that had to shift. Grey slots are free capacity.

Quick recall: Before reading further, cover the text above and say out loud: why does inserting at index 1 shift elements starting from the end?

Check your answer

Shifting from the end moves each element into a free slot. Starting from the front would overwrite the next element before it moved.

Finish the picture: a growing array

A dynamic array (like C++ vector or Python list) starts with capacity 2 and doubles when full. Append 1, 2, 3.

  • Appends 1 and 2. Predict: does anything get copied?
    Check

    No. Both fit in the 2 free slots: one write each.

  • Append 3. Predict what happens, since the array is full.
    Check

    A new block of capacity 4 is allocated, 1 and 2 are copied over, then 3 is written. That one append cost 3 writes.

Quick recall: Without scrolling up, say out loud: why did the third append cost more than the first two?

Check your answer

The array was full, so a bigger block had to be allocated and both old elements copied into it before writing the new one.

The rule in plain words

To read or write index i, compute its address and go there directly.

To insert at i, shift everything from i onwards one slot right, then write. To delete at i, shift everything after it one slot left.

Quick recall: Before you read the code, write the three steps of "insert at index i" in your own words. Then check below.

Check your answer

1. Starting from the last element, move each element one slot right until you reach index i. 2. Write the new value at index i. 3. Increase the size by one.

The code

Read this code after you have done the operations by hand. It will make more sense.

int insert_at(int a[], int n, int idx, int v) {   /* returns the new size */
    for (int i = n; i > idx; i--)
        a[i] = a[i - 1];                           /* shift right, from the end */
    a[idx] = v;
    return n + 1;
}

int delete_at(int a[], int n, int idx) {          /* returns the new size */
    for (int i = idx; i < n - 1; i++)
        a[i] = a[i + 1];                           /* shift left */
    return n - 1;
}

Definition

What you just did, in one line: an array stores $n$ elements of the same size in one contiguous block, so element $i$ is found by address arithmetic.

A dynamic array also keeps spare capacity and doubles it when full.

flowchart TD
  A["insert v at index i"] --> B{"Is the array full?"}
  B -->|yes| C["Allocate double capacity, copy all elements"]
  B -->|no| D["Shift a[i..n-1] one slot right"]
  C --> D
  D --> E["a[i] = v, n = n + 1"]
          
Before you scroll past this diagram, cover the right side and guess: inserting 7 at index 1 into [3, 8, 5] with capacity 5, which path do you take? (Answer: "no", straight to the shift box.)

How fast is it?

Quick recall: Before looking at the formula, predict: on an array of 1,000 elements, how many shifts does inserting at index 0 need?

Check your answer

1,000: every element moves one slot right.

Reading is fast because the address is one formula. With base address $b$ and element size $s$ bytes:

$$\text{address}(a[i]) \;=\; b + i \cdot s$$

For 4-byte ints starting at address 1000, a[3] lives at $1000 + 3 \cdot 4 = 1012$. No loop is needed, whatever $i$ is.

OperationCostWhy
Read / write a[i]$O(1)$One address computation
Search (unsorted)$O(n)$May have to check every element
Append at the end$O(1)$ amortizedOccasional doubling copies are spread over many appends
Insert at index $i$$O(n)$Shifts $n - i$ elements right
Delete at index $i$$O(n)$Shifts $n - i - 1$ elements left

Warning

C arrays do not check bounds. Writing a[5] in an array of 5 elements silently overwrites other memory. Always keep the size and capacity next to the array.

Exam tip

Learn the address formula $b + i \cdot s$ and the phrase "amortized $O(1)$ append": doubling copies $1 + 2 + 4 + \dots < 2n$ elements over $n$ appends.

Common mistakes

  • Shifting right starting from index i instead of from the end, which copies the same value over everything.
  • Forgetting to update the size after inserting or deleting.
  • Growing a dynamic array by +1 each time instead of doubling, which makes $n$ appends cost $O(n^2)$.

Try it yourself

Exercise 1 (warm-up)

An int array (4 bytes per element) starts at address 2000. What is the address of a[10]?

Hint

Use base + index × size.

Answer

$2000 + 10 \cdot 4 = 2040$.

Exercise 2 (a bit harder)

A dynamic array starts with capacity 1 and doubles when full. How many element copies happen in total during 8 appends? Write your answer down before you open it.

Show answer

Copies happen when growing from 1→2, 2→4 and 4→8: $1 + 2 + 4 = 7$ copies. That's fewer than 8 appends, which is why append is amortized $O(1)$.

Did you know?

Did you know?

Arrays are fast in practice for a second reason: CPU caches load memory in chunks of 64 bytes, so reading a[0] also brings a[1] to a[15] into cache for free.

That's why it's worth understanding: an $O(n)$ scan of an array often beats an "equal" $O(n)$ walk through a linked list by a factor of 10 or more.

Key points to remember

If you remember one thing from each line, remember the because.

  • Access is $O(1)$ because the address of a[i] is computed as $b + i \cdot s$.
  • Insert and delete in the middle are $O(n)$ because every later element has to shift by one slot.
  • Appending is amortized $O(1)$ because doubling makes the rare big copies add up to less than $2n$.
  • Shifting right must start from the end because otherwise you overwrite elements before moving them.
  • Arrays are cache-friendly because their elements sit next to each other in memory.

Self-check questions

Answer these in order, out loud. They follow the page, so your answers become the script of your video.

  1. How is an array like a row of numbered lockers?
    Sample answer

    You can walk straight to any locker number, but putting something new in the middle means moving everything after it one locker along.

  2. Walk through inserting 7 at index 1 in [3, 8, 5].
    Sample answer

    Move 5 from slot 2 to 3, then 8 from slot 1 to 2, then write 7 into slot 1: [3, 7, 8, 5].

  3. Why does the insert loop run from n down to idx + 1?

    A good answer says what would be overwritten otherwise.

    Sample answer

    Moving from the end puts each element into a slot that is already free. Going upwards would overwrite a[idx+1] with a[idx] before a[idx+1] had moved.

  4. So what is an array, in one sentence?
    Sample answer

    A contiguous block of same-sized elements where element $i$ is found directly by computing its address.

  5. Why is reading a[i] $O(1)$ whatever $i$ is?

    A good answer gives the address formula.

    Sample answer

    The address is base + i × element size, a single calculation, so no other element has to be visited.

  6. What do read, search, append, insert and delete cost?
    Sample answer

    Read/write $O(1)$, search $O(n)$, append amortized $O(1)$, insert and delete in the middle $O(n)$ because of shifting.

  7. Why is appending to a dynamic array still $O(1)$ on average when resizing copies everything?
    Sample answer

    Because capacity doubles, copies happen rarely and add up to less than $2n$ over $n$ appends, so each append costs a constant on average.

  8. If you had to teach this to a friend in 2 minutes, what would you leave out, and why?
    Sample answer

    I'd leave out the cache details and the amortized proof. I'd keep the locker analogy, the insert-with-shifting example, and "$O(1)$ access, $O(n)$ insert in the middle".

External resources

Videos

Review schedule

Come back after 3, 7, 14, then 30 days. Spacing beats cramming.

Feynman prompt

Explain this topic in your own words, as if teaching a beginner.

Explain it in your own words first, then record a 2–3 minute video on your phone.

25:00

Ready to record? Open your phone camera now and record a 2–3 minute explanation.

My notes

Saved only in this browser (localStorage). Nothing is uploaded.