Arrays & strings

An array keeps its items side by side, so any index is one step away. The same layout makes inserting in the middle expensive, and it explains why building a string one piece at a time can be slow.

3 examples14 to practiceRead · 8 min
The idea

An array is one block of memory, so every index is one step away

An array stores its items next to each other in one block of memory, and every item takes the same number of bytes. An item's position is its index, counted from 0: the first item is a[0], the fourth is a[3].

Because the items are packed and equal in size, the computer never has to walk the array to find one. It works out where item i lives:

address of a[i] = start of the block + i × size of one item

That is one multiplication and one addition, whether the array holds four items or four million. Reading or writing a[i] costs O(1).

Index

Address

Value

0

1000

3

1

1004

8

2

1008

1

3

1012

5

The table is an array of four whole numbers, 4 bytes each, starting at address 1000. Item 3 is at 1000 + 3 × 4 = 1012. This is also why indexes start at 0: the index is how many items you skip from the start.

The price of this layout is that the block is one piece. There is no room between two neighbors, and the block has a fixed size, chosen when it is created. The next three sections are about what that costs.

The catch

Inserting in the middle moves every item after it

Say the array holds 3, 8, 1, 5 and has one free cell at the end. You want 9 at index 1, between 3 and 8. There is no gap there, so you have to make one: every item from index 1 onward moves one cell to the right, and then 9 goes into the cell that opened up.

The moves run from the end backward. If you went left to right, copying 8 into index 2 would overwrite the 1 before it had moved anywhere.

Inserting at index i into n items moves n − i of them:

  • at the front (i = 0), all n items move;
  • in the middle, about half of them;
  • at the end (i = n), none do, as long as there is a free cell.

The rule

Reading or writing any index takes one step. Inserting or deleting anywhere but the end moves every item after that spot, so it costs O(n).

The mirror image

Deleting closes the gap the same way, in the other direction

Deleting the item at index i leaves a hole, and an array can't have holes: its items must stay packed for the address rule to work. So every item after i moves one cell to the left. This time the moves run from the hole forward, so each item is copied before its own cell is overwritten.

The last cell ends up holding a copy of the item before it, so the code clears it and the array's length drops by one. Like insertion, deleting at the front moves everything, and deleting at the end moves nothing.

When the order of the items doesn't matter (a bag of tasks, a set of players), there is a faster way: move the last item into the hole and shorten the array by one. That is one copy instead of n − i, so it costs O(1).

The limit

When the block is full, the whole array moves

The block can't simply get longer, because the memory right after it may already belong to something else. A dynamic array (Python's list, Java's ArrayList, C++'s std::vector) handles this by keeping some spare cells. Its capacity is how many cells the block has; its length is how many are in use.

Appending while there is spare capacity is one write: O(1). When the block is full, the next append has to do more work:

  1. Get a bigger block

    Allocate a new block, a multiple of the old capacity (1.5 or 2 times is common).

  2. Copy every item

    All n items are copied across, in order: O(n).

  3. Let the old block go

    The old block is freed for other uses.

  4. Write the new item

    The append that triggered the move finally lands.

A copy of every item sounds expensive, and that one append is. But because the capacity grows by a multiple, the copies get rarer as the array grows, and spread over all the appends they add only a constant amount to each. Appending is O(1) on average, or amortized. Space & amortized analysis works through why.

Strings

A string is an array of characters that you usually can't change

A string is a sequence of characters stored like an array, so s[i] is a constant-time lookup and length of s is known without counting.

One caveat about what a "character" is. Java and JavaScript store strings as UTF-16, and their length counts 16-bit code units. Most characters take one unit, but an emoji or a rarer script takes two, so "😄".length is 2 and an index can land on half of one. Python indexes whole characters (code points).

In Java, Python and JavaScript, strings are immutable: once created, a string never changes. Code that seems to change one, like s ← s + c, builds a new string and copies both parts into it. In a loop, that copying adds up.

Building a string of n characters one at a time copies 1 + 2 + … + n = n(n + 1) ÷ 2 characters: about 50 million for a 10,000-character string. That is O(n²) for a job that should be O(n).

The fix is to collect the pieces in something you can change and turn it into a string once at the end:

Language

Collect the pieces in

Then

Java

a StringBuilder

toString()

Python

a list

"".join(pieces)

JavaScript

an array

pieces.join("")

Some runtimes speed up repeated + in special cases, but the language doesn't promise it, so don't count on it.

In practice

Arrays are fast to read and slow to reshape

Operation

Cost

Why

Read or write a[i]

O(1)

The address is computed

Find a value (unsorted)

O(n)

Check items one by one

Append at the end

O(1) on average

An occasional full copy

Insert or delete at index i

O(n − i)

Items after i shift

Insert or delete at the front

O(n)

Every item shifts

The table undersells one thing: arrays are fast in practice even when you visit every item. The processor fetches memory in small blocks (cache lines), so reading one item brings its neighbors along, and walking an array front to back is about the friendliest thing you can ask of the hardware.

Arrays are the right default for most lists. When a job needs many inserts and deletes in the middle, Linked lists trade the shifting for slower lookups by index.

Check yourself

4 questions

1. An array holds 1,000 items and has room for more. How many items move when you insert a new one at index 0?
2. Why does insertion shift items starting from the end of the array?
3. You build a 10,000-character string by adding one character at a time with s ← s + c, in a language whose strings are immutable. Roughly how many characters get copied?
4. The order of the items doesn't matter. What is the cheapest way to delete the item at index i from an array of n items?
Takeaways

Remember this

  • An array's items sit side by side, so the address of any index is computed in one step: reading or writing a[i] is O(1).
  • Inserting or deleting at index i shifts every item after it, which is O(n) at the front and free at the end.
  • A dynamic array grows by copying itself into a bigger block; because the block grows by a multiple, appends cost O(1) on average.
  • Strings in Java, Python and JavaScript are immutable, so building one with + in a loop is O(n²); collect the pieces and join once.
Practice

Now solve some yourself

Only this topic 0 of 13 solved

  1. Easy
    Remove every copy of a value in place
    Arrays & strings
  2. Easy
    Fewest raises to make a list never decrease
    Arrays & strings
  3. Easy
    Shorten long words to first letter, count, last letter
    Arrays & strings
  4. Easy
    Add one to a number stored digit by digit
    Arrays & strings
  5. Easy
    Longest run of one letter in a DNA string
    Arrays & strings
  6. Easy
    Build rows of Pascal's triangle
    Arrays & strings
  7. Easy
    Best single buy and sell
    Arrays & strings
  8. Easy
    Length of the last word
    Arrays & strings
  9. Easy
    Longest start shared by every word
    Arrays & strings
  10. Medium
    Rotate a list by k places
    Arrays & strings
  11. Medium
    Read a grid in a spiral
    Arrays & strings
  12. Medium
    Turn a square grid a quarter turn in place
    Arrays & strings
  13. Medium
    One step of the Game of Life, in place
    Arrays & strings

Mixed with topics you have practiced 0 unlocked

    1 more once you have practiced Hash tables & maps. Play one of its examples to the end and solve one of its problems to unlock them.

    Problems open on their own sites (LeetCode, Codeforces, CodeChef, AtCoder, CSES) in a new tab. Tick Solved when you get it accepted there; your ticks decide which mixed problems open next.

    Next

    Space & amortized analysis

    Continue