Why is a Java array fixed-length once created, and how do you handle a collection that needs to grow?
answer
- length fixed at new, immutable
- contiguous memory = O(1) index
- grow = allocate bigger + copy
- Arrays.copyOf / System.arraycopy
- ArrayList ~1.5x, amortized O(1) add
basics
~20 sAn array's length is set when you create it and can never change. To grow, you either create a bigger array and copy the elements over, or use a class like ArrayList that does that copying for you.
solid answer
~40 sWhen you allocate an array with new T[n], the JVM reserves one contiguous block of memory sized for exactly n elements and stores that count in the immutable length field. Because the elements sit back-to-back in memory, there is nowhere to append a slot without potentially colliding with other data, so the length is fixed for the array's lifetime. There is no resize or append operation. To 'grow', you allocate a new, larger array and copy the old contents (Arrays.copyOf or System.arraycopy do this efficiently). In practice you reach for java.util.ArrayList, which wraps an internal array and transparently reallocates to a larger one (typically ~1.5x) when full, giving amortized O(1) appends. The fixed-length design keeps arrays simple, cache-friendly, and fast, while collections layer growth on top.
go deeper
Knows array length cannot change and that ArrayList is the go-to when you need to add or remove elements dynamically.
Explains the resize-and-copy mechanism (Arrays.copyOf) and that ArrayList wraps an array, reallocating when full.
Ties fixed length to the contiguous memory layout, explains geometric growth giving amortized O(1) adds, and distinguishes capacity from size.
Reasons about growth-factor trade-offs (memory waste vs. copy frequency), pre-sizing with initial capacity for hot paths, and when a primitive array beats ArrayList for memory/cache.
## What 'fixed-length' means When you write `int[] a = new int[5];`, the JVM allocates a **single contiguous region of memory** large enough for exactly five `int` slots, plus a header that records the **length** (5). That length is stored once at creation and is **immutable** — `a.length` is a `final`-like field with no setter, and there is no method to append, insert, or remove a slot. The array is the same size for its entire lifetime. ## Why it has to be this way Arrays are **contiguous**: element `i` lives at `base_address + i * element_size`. This layout is what makes indexing O(1) (one multiply-and-add) and extremely **cache-friendly** (neighboring elements are physically adjacent in memory). But it also means the memory immediately *after* the last element may already be occupied by other objects. You cannot simply 'extend' the block in place because there is no guarantee the next bytes are free. Allowing resize would force either reserving slack (wasteful) or relocating on every growth (which is exactly what the resize-and-copy approach does explicitly). So arrays choose the simple, predictable contract: **fixed size, O(1) access, no growth.** ## How to grow anyway There are two layers: **1. Manual resize-and-copy.** Allocate a new bigger array and copy: ```java int[] a = {1, 2, 3}; int[] bigger = java.util.Arrays.copyOf(a, 6); // [1,2,3,0,0,0] bigger[3] = 4; ``` `Arrays.copyOf` (and the lower-level `System.arraycopy`) copy the elements in bulk. Each copy is O(n). **2. Use a growable collection.** `java.util.ArrayList<T>` is the idiomatic choice. Internally it *holds* an array and, when you `add` past its capacity, it allocates a new array (commonly about **1.5x** the old capacity) and copies. Because growth happens geometrically (not by 1 each time), the cost is spread out: **amortized O(1)** per append even though individual resizes are O(n). `ArrayList` also offers `remove`, `size`, and bounds-checked `get`/`set` that throw `IndexOutOfBoundsException` on a bad index, just like arrays. ## Capacity vs. size A subtle point for `ArrayList`: its **capacity** (the backing array length) is usually larger than its **size** (the number of elements actually stored). `size()` is what you iterate over; capacity is an internal optimization to reduce the number of reallocations. ## Key takeaways - Array length is set at `new` and never changes; no append/resize exists. - The reason is the contiguous, O(1)-indexable, cache-friendly memory layout. - Grow by copying into a bigger array, or let `ArrayList` do amortized-O(1) growth for you. - `ArrayList` capacity (backing array) differs from its size (element count).
- Why is ArrayList.add amortized O(1) even though resizing copies all elements?Because capacity grows geometrically (e.g. 1.5x), resizes become exponentially rarer as the list grows. The total copy work across n adds is bounded by ~2n, so the cost per add averages to a constant.
- What is the difference between an ArrayList's size and its capacity?Size is the number of elements you have actually added (what size() and iteration use). Capacity is the length of the internal backing array — usually larger — and exists to avoid reallocating on every add.
saying these in an interview costs you the question
- Claiming you can resize an array in place or that length has a setter
- Confusing ArrayList capacity with its size()
- Saying ArrayList grows by 1 each add (it grows geometrically)
- Believing each ArrayList.add is O(n) rather than amortized O(1)