02 Arrays and Dynamic Arrays

Learn how arrays store ordered data, how indexing and shifting affect performance, and how dynamic, multidimensional, and array-based structures support different workloads.

The model and indexed access

An stores an ordered sequence of elements, and each element is identified by an integer index. In zero-based indexing, the first element is at index 00, the second is at index 11, and an of length nn ends at index n−1n-1.

For an A=[17,4,29,8]A = [17, 4, 29, 8], the value at A[2]A[2] is 2929. The index 22 identifies a position; it is not the same thing as the value stored there. Index 44 is outside the valid range of this four-element .

In a conventional , elements of the same type occupy adjacent memory locations. If the first element begins at address BB, each element uses ww bytes, and the requested index is ii, then the address is:

address⁡(A[i])=B+i×w\operatorname{address}(A[i]) = B + i \times w

Because this calculation does not depend on the 's length, reading or updating a known index typically takes O(1)O(1) time. Searching for an unknown value is different: the program may need to inspect up to nn elements, so a linear search can take O(n)O(n) time.

Takeaway: Arrays provide fast positional access because an index can be converted directly into a memory address.

and basic algorithms

visits elements one after another, commonly from index 00 through index n−1n-1. It supports tasks such as printing values, computing a total, finding a maximum, validating data, or applying the same operation to every element.

A running-total can be described as follows:

  • Set a total to 00.

  • For each valid index ii, add A[i]A[i] to the total.

  • Return the total after the final element.

A of nn elements takes O(n)O(n) time. If the algorithm stores only a running total and a loop index, its additional space usage is O(1)O(1).

Takeaway: Visiting every element is linear in the number of elements, even though accessing each individual element is constant time.

Insertion, deletion, and shifting

Insertion preserves the order of existing elements by making room for a new value. To insert at index ii, elements from the end toward index ii must move one position to the right. Moving from right to left prevents a value from being overwritten before it is copied.

For example, inserting 2525 at index 22 changes [10,20,30,40][10, 20, 30, 40] to [10,20,25,30,40][10, 20, 25, 30, 40]. The values 3030 and 4040 shift right. Inserting at the beginning or middle can require moving O(n)O(n) elements, so the worst-case time is O(n)O(n). Appending at the end is O(1)O(1) when unused is available.

Deletion removes an element and closes the gap by shifting later elements one position to the left. Deleting index 11 from [10,20,30,40][10, 20, 30, 40] produces [10,30,40][10, 30, 40]. Deleting from the beginning or middle can take O(n)O(n), while deleting the final element takes O(1)O(1) because no other element must move.

Takeaway: Arrays are efficient for access and end operations, but insertions and deletions near the beginning or middle are expensive because of shifting.

Fixed storage and dynamic growth

A has storage determined at creation time. It is appropriate when the required number of elements is known and stable, such as a fixed- lookup table. Its predictable storage can be useful when memory requirements must remain stable.

A uses an ordinary as backing storage while tracking two quantities. is the number of elements currently stored. is the number of elements that fit in the allocated storage. For example, a can have 44 and 88: four slots contain logical elements and four slots remain available for future appends.

When a becomes full, it generally allocates a larger backing , copies the existing elements, replaces the old backing , and then inserts the new element. A common policy multiplies by a constant factor, such as 22, but the exact growth policy depends on the implementation.

Takeaway: Fixed-length arrays prioritize stable, predetermined storage; dynamic arrays trade some extra for the ability to grow.

Amortized cost of appending

An append to a normally takes O(1)O(1) time when is available. During a resize, copying the existing nn elements takes O(n)O(n) time, so one particular append can be expensive.

explains why the long-run cost remains small. If grows geometrically, resizes occur less frequently as the structure becomes larger. Across many appends, the total copying work is proportional to the total number of inserted elements, so the average cost per append is amortized O(1)O(1).

The main trade-off is memory: unused improves future append performance but can occupy more storage than the current . Implementations may offer operations such as ensureCapacity to reserve space or trimToSize to reduce unused .

For the broader operation profile:

  • Indexed access and indexed update are typically O(1)O(1).

  • takes O(n)O(n).

  • Insertion or deletion at the beginning or middle takes O(n)O(n).

  • Appending is amortized O(1)O(1), but worst-case O(n)O(n) during a resize.

Takeaway: Amortized performance describes the sequence-level cost, not the cost of every individual append.

Multidimensional arrays and matrix layout

A uses more than one index. A two-dimensional can represent rows and columns, for example:

M=[357246]M = \begin{bmatrix} 3 & 5 & 7 \\ 2 & 4 & 6 \end{bmatrix}

With zero-based indexing, M[1][2]=6M[1][2] = 6: row 11, column 22. Traversing a rectangular matrix uses nested loops. If the matrix has rr rows and cc columns, visiting every element takes O(r×c)O(r \times c) time.

A rectangular matrix can also be flattened into one one-dimensional . With cc columns, the row-major mapping is:

flatIndex⁡=row⁡×c+column⁡\operatorname{flatIndex} = \operatorname{row} \times c + \operatorname{column}

For row 11, column 22 in a matrix with 33 columns, the flat index is 1×3+2=51 \times 3 + 2 = 5. Nested arrays can be easier to read and can support rows of different lengths, sometimes called a jagged . Flat storage makes the layout explicit and can improve memory locality.

Takeaway: Choose nested or flat multidimensional storage according to whether readability, variable row lengths, or a compact layout matters most.

Choosing an -based structure

Arrays are a strong choice when data is ordered, frequently accessed by position, or repeatedly traversed. Common uses include scores, measurements, sensor readings, character sequences, table rows, pixels, grids, matrices, lookup tables, and precomputed values. Arrays also provide a foundation for structures such as stacks, queues, and heaps.

Choose a when the is known in advance, the number of elements will not change, and predictable memory use is important. Choose a when the collection grows or shrinks and fast indexed access remains important. Dynamic arrays are particularly suitable when appending is more common than inserting or deleting near the beginning or middle.

Consider another data structure when frequent middle operations dominate the workload, because shifting can make those operations O(n)O(n). The central design question is whether the workload benefits more from fast indexed access and compact or from efficient changes to the collection's interior.

Final takeaway: Use an -based structure when ordered storage, fast access by index, and efficient are central requirements; select fixed or dynamic storage based on whether the collection's is stable or changing.