3 Arrays and Dynamic Arrays

A progressive guide to array storage, indexing, traversal, updates, dynamic resizing, amortized analysis, and practical data-structure trade-offs.

1. Structure and indexed access

An stores elements in an ordered sequence. Each element has a position called an index, and indexing normally begins at 00. Therefore, an with nn elements uses indices from 00 through n−1n-1.

For example, in the sequence [18,7,25,4][18,7,25,4], the element at index 22 is 2525. The index identifies the position; it does not indicate the element’s value.

elements are stored in . If every element occupies the same number of bytes, the address of element ii can be computed directly:

address(array[i])=base_address+i×element_size\text{address}(\text{array}[i])=\text{base\_address}+i\times\text{element\_size}

Because the address calculation takes a constant number of steps, accessing or replacing an element by index takes O(1)O(1) time. Contiguous storage also tends to provide good , which supports efficient sequential processing and keeps memory overhead relatively low.

Takeaway: Direct indexing is fast because an combines zero-based positions with a predictable, contiguous layout.

2. and searching

means visiting elements in sequence, often from the first position to the last. A complete of nn elements takes O(n)O(n) time because every element is processed once. If no additional structure that grows with nn is created, the auxiliary space is O(1)O(1).

A search can also use a . checks one element at a time and returns the matching index when it finds the target. If the first element matches, the best-case time is O(1)O(1). If the target is last or absent, the worst-case time is O(n)O(n).

The same complexity applies to both fixed arrays and dynamic arrays because both require checking positions one at a time when the data is unsorted.

Takeaway: Visiting or searching every possible position is linear in the number of elements, even though accessing one known position is constant time.

3. Fixed- updates

A fixed- receives its when it is created. Its length cannot be increased directly. If the is full and another element is needed, a larger must be created and the existing elements copied into it.

Insertion must preserve the order of elements. To insert at index ii, elements from the end down to index ii are shifted one position to the right. Inserting at the end takes O(1)O(1) when unused space is available, but inserting at the beginning or middle can shift O(n)O(n) elements and therefore takes O(n)O(n) time.

Deletion works in the opposite direction. After deleting index ii, later elements shift one position to the left. Deleting the last element takes O(1)O(1) when order is maintained, while deleting from the beginning or middle takes O(n)O(n).

If order does not matter, a deleted element can sometimes be replaced with the last element before reducing the logical length. This can make deletion O(1)O(1), but it changes the order of the remaining elements.

Takeaway: updates are inexpensive at the end but expensive away from the end because order requires shifting elements.

4. Dynamic growth and amortized appending

A combines indexed access with automatically managed storage. It maintains both , the number of elements currently stored, and , the number of positions available in its backing . For example, =3=3 and =5=5 means that three positions are occupied and two more are available without allocation.

When an element is appended and is less than , the element is placed in the next free position, so the operation takes O(1)O(1) time. When the backing is full, the structure allocates a larger , copies or moves the existing elements, and then inserts the new element. That particular append takes O(n)O(n) time.

Dynamic arrays usually grow geometrically. A common illustrative rule is:

new capacity=max⁡(1,2×old capacity)\text{new capacity}=\max(1,2\times\text{old capacity})

The exact growth factor is an implementation detail. The important principle is that should grow by meaningful multiples rather than by one position at a time. If increased by only one position for every append, the total copying across nn appends would be proportional to 1+2+3+⋯+(n−1)1+2+3+\cdots+(n-1), which is O(n2)O(n^2). With geometric growth, the total copying across a long sequence of appends is O(n)O(n), giving an of O(1)O(1) per append.

A may also shrink when its becomes much smaller than its . Shrinking too aggressively can cause thrashing, in which repeated growth and shrink operations waste time. A threshold-based policy avoids shrinking after every small deletion.

Takeaway: An occasional expensive resize is acceptable because geometric growth spreads its cost across many appends.

5. Choosing the right strategy

The main operation costs can be summarized as follows:

  • Access by index: O(1)O(1) for both fixed and dynamic arrays.

  • Update by index: O(1)O(1) for both structures.

  • : O(n)O(n).

  • : O(n)O(n) in the worst case.

  • Append with available : O(1)O(1).

  • Append that causes a resize: O(n)O(n) for that individual operation.

  • Append in a , amortized: O(1)O(1).

  • Insert or delete at the beginning or middle: O(n)O(n).

  • Delete at the end: O(1)O(1).

Dynamic arrays are a strong default when a program needs fast indexed access, efficient , compact storage, and frequent appends near the end. They are less suitable when insertions or deletions near the beginning or middle occur frequently, because shifting elements remains linear.

If the final number of elements is known or can be estimated, reserving in advance can reduce reallocations. Reservation should generally happen in meaningful batches; reserving one additional position before every append can undermine the benefit of geometric growth.

Final takeaway: Choose arrays or dynamic arrays when direct access, sequential processing, and end-based growth matter most. Consider another structure when frequent middle updates matter more than compact contiguous storage.