Dynamic Array Amortized Analysis

A dynamic array automatically grows when you try to make an insertion and there is no more space left for the new item. Usually, the array doubles in size.

Additional functionality often comes with a cost. Here, we need to do some tricky things under the hood when we run out of room. When we don't have any space for a new item, we have to allocate a bigger array and copy over all of the elements from the array we've outgrown before we can finally append our item.

Doing all that copying takes time, where n is the number of elements in our array.

Oof. That's an expensive cost for an append. In a fixed-length array, appends only take time!

But appends take time only when we insert into a full array. And that's pretty rare, especially if we double the size of the array every time we run out of space. So in most cases appending is still time, and sometimes it's time.

So focusing on a single append seems a bit harsh. Sure, the append could be expensive, but it's far more likely it'll be cheap. A more fair analysis would look at the cost of a single append averaged over a large number of appends. This is called amortized analysis.

"Amortize" is a fancy verb used in finance that refers to paying off the cost of something gradually. With dynamic arrays, every expensive append where we have to grow the array "buys" us many cheap appends in the future. Conceptually, we can spread the cost of the expensive append over all those cheap appends.

Here, instead of looking at the worst case for an append individually, let's look at the overall cost of doing many appends—let's say m appends.

The cost of doing m appends is m (since we're appending every item), plus the cost of any doubling when the dynamic array needs to grow. How much does the doubling cost?

Say we start off with space for just one item. Then the first doubling costs 1. The second costs 2. The third costs 4. The fourth costs 8.

So, the cost we're looking at is:

1 + 2 + 4 + 8 + ... + \frac{m}{2} + m

It's helpful to look at that from right to left:

m + \frac{m}{2} + \frac{m}{4} + ... + 4 + 2 + 1

We can see what this comes to when we draw it out. If this is m:

A square with the letter m in the middle.

\frac{m}{2} is half the size:

The same square from the previous diagram with another rectangle just as tall and half as wide attached to the right of it. This rectangle is labeled "m/2".

\frac{m}{4} is half the size of that:

Another rectangle labeled "m/4" just as wide and half as tall as the "m/2" rectangle is added.

And so on:

Another rectangle labeled "m/8" just as tall and half as wide as the "m/4" rectangle is added, and the pattern continues. The added rectangles (m/2, m/4, m/8, etc) together form a square the same size as the original, m.

We see that the whole right side ends up being another square of size m, making the sum 2m.

So when we do m appends, the appends themselves cost m, and the doubling costs 2m. Put together, we've got a cost of 3m, which is . So on average, each individual append is . m appends cost us .

Remember, even though the amortized cost of an append is , the worst case cost is still . Some appends will take a long time.

What's next?

If you're ready to start applying these concepts to some problems, check out our mock coding interview questions.

They mimic a real interview by offering hints when you're stuck or you're missing an optimization.

Try some questions now

. . .