Why Doubling Works

Growing by a fixed number of slots made add cost amortized. The problem is that grow gets called just as often no matter how large the array is. Doubling does not have that problem. The bigger the array, the more room a doubling adds, so grow gets called less and less often as the array grows.

Let’s redo the calculation from the previous section with doubling. Start with an empty DynamicArray whose underlying array has capacity , and do one million adds, where grow doubles the capacity each time the array fills.

A grow happens only when the array is full. One arrow per copied element:

┌────┐
│ 10 │
└────┘
  ↓                                        2nd add copies 1
┌────┬────┐
│ 10 │ 20 │
└────┴────┘
  ↓    ↓                                   3rd add copies 2
┌────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │
└────┴────┴────┴────┘
  ↓    ↓    ↓    ↓                         5th add copies 4
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │ 70 │ 80 │
└────┴────┴────┴────┴────┴────┴────┴────┘
  ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓     9th add copies 8
                                           ⋮

The 1st add goes into the capacity- array and fills it. Nothing is copied. The 2nd add finds the array full, so grow copies element. The 4th add has room, so we just place the element. The 6th through 8th adds have room too. The adds between grows copy nothing. This continues until the add finds the array full, so grow copies elements (capacity ). The through millionth adds have room.

As before, most of the work is the copying done inside grow. The successive grow calls copy , then , then , then elements, and so on. Each grow copies twice as many elements as the one before it, because each grow doubles the capacity and the array fills up again before the next grow.

Now let’s do it in general, for adds. To keep the arithmetic simple, take to be a power of two. The total copying is

Why does the sum stop at ? The largest array we ever copy is the one of capacity . Filling it triggers a grow to capacity , which copies its elements. The array of capacity is never grown, because the add fills its last slot without overflowing. So is the last term.

Written with exponents, the terms are:

There are of them. (Recall that here means , and that , so .)

This is a geometric series. The sum of the first terms of a geometric series with first term and common ratio is

Here , , and . Plugging in:

So the total copying over adds is , which is . The amortized cost of add is then

With a fixed increment, the number of grow calls grows linearly with the number of adds. With doubling, each grow doubles the number of adds we can do before the next grow, so the number of grow calls is only . That is why the copying sum is geometric rather than arithmetic. A geometric series with ratio sums to less than twice its last term, and its last term is , so the total is rather than .

The factor of itself is not the point. Growing by a factor of , or , would work just as well. Any multiplicative factor greater than makes the number of grow calls logarithmic in the number of adds, and that makes the total copying a geometric series. So the rule to remember is to multiply the capacity, not add to it.