Skip to main content

Why Dynamic Arrays Double Their Size (ArrayList / std::vector)

🎯 The Question

"When a dynamic array (like Java ArrayList or C++ std::vector) runs out of capacity, why does it double its size (2×2\times geometric growth) instead of growing by a fixed amount (like +10+10 or +1000+1000 elements)?"


⚡ 30-Second Elevator Pitch

Arrays require contiguous physical memory. When an array is full and a new element is appended:

  1. The allocator cannot simply expand in place (adjacent RAM might be occupied).
  2. It must allocate a brand new, larger block of RAM elsewhere, copy all NN existing elements over, and free the old block.
  • If size grew by +10+10 elements (Fixed Growth): Inserting NN elements requires N10\frac{N}{10} full memory copies, resulting in O(N2)O(N^2) Quadratic Time—catastrophically slow.
  • If size doubles (2×2\times Geometric Growth): Resizing happens exponentially less often (1,2,4,8,16,1, 2, 4, 8, 16, \dots). The total copies to insert NN items is: N+N2+N4+<2NN + \frac{N}{2} + \frac{N}{4} + \dots < 2N This guarantees an Amortized O(1)O(1) Constant Time per push_back().

🧠 Under-the-Hood: Geometric Resizing & Amortized O(1)O(1)


🔬 Mathematical Proof: Aggregate Method

To insert NN elements into a doubling array:

  • Cost of inserting NN raw elements: NN writes.
  • Cost of copying elements during resizes: Copies=1+2+4+8++N2=N1\text{Copies} = 1 + 2 + 4 + 8 + \dots + \frac{N}{2} = N - 1
  • Total Operations: Total Cost=N+(N1)=2N1\text{Total Cost} = N + (N - 1) = 2N - 1
  • Amortized Cost per Operation: Amortized Cost=2N1N2=O(1)\text{Amortized Cost} = \frac{2N - 1}{N} \approx 2 = O(1)

📌 Comparison Matrix: Fixed Growth vs. Geometric Growth

Growth StrategyTotal Copy Work for NN InsertsAmortized Cost per append()Memory Waste Overhead
Fixed Increment (+K+K)N22K=O(N2)\approx \frac{N^2}{2K} = O(N^2)🐢 O(N)O(N) LinearMinimal (+K+K slots)
2.0×2.0\times Growth (Java / C++)2N=O(N)\approx 2N = O(N)O(1)O(1) ConstantMax 50% unused capacity
1.5×1.5\times Growth (MSVC / Folly)3N=O(N)\approx 3N = O(N)O(1)O(1) ConstantMax 33% unused capacity (Memory recycling friendly)

💡 What Interviewers Ask Next (Follow-Up Traps)

  1. "Why do some implementations (like MSVC std::vector and Facebook's FBVector) use a 1.5×1.5\times growth factor instead of 2.0×2.0\times?"

    • Answer: With a 2.0×2.0\times factor, the new allocated memory block is always strictly larger than the sum of all previously freed memory chunks (2k>i=0k12i2^k > \sum_{i=0}^{k-1} 2^i), preventing the memory allocator from reusing previously deallocated memory. A growth factor of 1.5×1.5\times (or the Golden Ratio ϕ1.618\phi \approx 1.618) allows the allocator to reuse previously freed memory segments, reducing fragmentation.
  2. "How do you eliminate all reallocation overhead in production?"

    • Answer: Call reserve(expected_size) before inserting elements. This pre-allocates contiguous memory upfront, reducing resize operations and copying overhead to absolute zero.

Placement & Interview Takeaway

Interview Answer: Dynamic arrays double their capacity because geometric progression ensures that the total number of element copies across NN insertions is bounded by 2N2N. This mathematical property yields an amortized O(1)O(1) insertion time, whereas growing by a fixed constant incurs an O(N2)O(N^2) copying penalty.


📺 Video Explanation

💬

Discussion & Doubts