Topic Overview

Arrays

Master arrays: the most fundamental data structure. Learn operations, time complexity, dynamic arrays, and when to use arrays vs other structures.

Beginner15 min read

Arrays

Why This Matters

Arrays are the foundation of almost every data structure. They're the simplest way to store a collection of elements, and understanding them deeply is essential because most other data structures are built on top of arrays or use array-like concepts.

Arrays matter because they provide O(1) random access—you can access any element by its index instantly. This makes arrays ideal for algorithms that need to jump to specific positions, like binary search or quick sort. Arrays also have excellent cache locality—elements are stored contiguously in memory, so accessing one element often brings nearby elements into the CPU cache, making sequential access very fast.

In interviews, array problems are everywhere. Two-pointer technique, sliding window, prefix sums, and many other patterns rely on arrays. Real-world systems use arrays extensively: databases use arrays for indexing, operating systems use arrays for process tables, and compilers use arrays for symbol tables. Understanding arrays deeply helps you understand why other data structures make the trade-offs they do.

What Engineers Usually Get Wrong

Most engineers think "arrays are simple, I know them." But they miss critical details. Arrays have O(1) access time, but insertion and deletion in the middle are O(n) because you must shift elements. Many engineers forget this and use arrays for frequent insertions/deletions, then wonder why their code is slow.

Engineers also confuse static arrays (fixed size) with dynamic arrays (growable). Static arrays are allocated once and can't grow. Dynamic arrays (like Python's list or JavaScript's Array) can grow, but growing requires allocating a new larger array and copying all elements—an O(n) operation. Dynamic arrays amortize this cost by growing by a factor (usually 2x), making average insertion O(1) amortized.

Another common mistake is not understanding memory layout. Arrays store elements contiguously in memory. This is great for cache performance, but it means you can't easily insert in the middle without shifting. Linked lists solve this problem but lose random access. Understanding this trade-off is crucial.

How This Breaks Systems in the Real World

A service was using a dynamic array to store user sessions. Each time a user logged in, the service appended to the array. This worked fine for months. Then, during a traffic spike, the array grew from 10,000 to 100,000 elements. The dynamic array had to resize multiple times, each resize copying all existing elements. The service spent 80% of its CPU time copying arrays instead of serving requests. The service became unresponsive.

The fix? Pre-allocate the array with a reasonable initial capacity, or use a different data structure (like a hash table) if you don't need ordered access. But the real lesson is: dynamic arrays have hidden costs. Resizing is expensive, and you need to account for it.

Another story: A service was using a static array to store configuration values. The array had 100 slots. The service assumed it would never need more than 100 configurations. But after a year, the service needed 101 configurations. The array overflowed, causing a buffer overflow that corrupted memory. The service crashed. The fix? Use dynamic arrays or check bounds before writing. But the real lesson is: static arrays are dangerous if you can't guarantee the maximum size.


What is an Array?

An array is a collection of elements stored in contiguous memory locations. Each element can be accessed directly using its index (position). Arrays are zero-indexed in most languages (first element is at index 0).

Key Characteristics

  • Fixed or Dynamic Size: Static arrays have fixed size; dynamic arrays can grow
  • Contiguous Memory: Elements are stored next to each other in memory
  • Random Access: O(1) access time by index
  • Homogeneous Elements: All elements are typically the same type (in statically-typed languages)

Array Operations

Access (Read)

Access an element by its index.

1const arr = [10, 20, 30, 40, 50];
2
3// Access element at index 2
4const value = arr[2]; // 30
5
6// Time: O(1) - direct memory access
7// Space: O(1)
8// Edge cases: index out of bounds

Update (Write)

Update an element at a specific index.

1const arr = [10, 20, 30, 40, 50];
2
3// Update element at index 2
4arr[2] = 35; // [10, 20, 35, 40, 50]
5
6// Time: O(1)
7// Space: O(1)
8// Edge cases: index out of bounds

Insert

Insert an element at a specific position. Requires shifting elements.

1// Insert at beginning
2function insertAtBeginning(arr: number[], value: number): number[] {
3 arr.unshift(value); // O(n) - shifts all elements
4 return arr;
5}
6
7// Insert at end
8function insertAtEnd(arr: number[], value: number): number[] {
9 arr.push(value); // O(1) amortized for dynamic arrays
10 return arr;
11}
12
13// Insert at middle

Delete

Remove an element from a specific position. Requires shifting elements.

1// Delete at beginning
2function deleteAtBeginning(arr: number[]): number[] {
3 arr.shift(); // O(n) - shifts all elements
4 return arr;
5}
6
7// Delete at end
8function deleteAtEnd(arr: number[]): number[] {
9 arr.pop(); // O(1) for dynamic arrays
10 return arr;
11}
12
13// Delete at middle
14function deleteAtMiddle(arr: number[ index

Find the index of an element (linear search).

1// Linear search
2function linearSearch(arr: number[], target: number): number {
3 for (let i = 0; i < arr.length; i++) {
4 if (arr[i] === target) {
5 return i;
6 }
7 }
8 return -1; // Not found
9}
10
11// Time: O(n) - worst case checks all elements
12// Space: O(1)
13// Edge cases: empty array, target not found, duplicate values

Dynamic Arrays

Dynamic arrays (also called resizable arrays or vectors) can grow and shrink as needed. They're implemented using static arrays internally, but automatically resize when capacity is exceeded.

How Dynamic Arrays Work

  1. Initial Capacity: Start with a small array (e.g., capacity 4)
  2. Growth Strategy: When full, allocate a larger array (usually 2x current size)
  3. Copy Elements: Copy all elements from old array to new array
  4. Update Reference: Point to the new array

Implementation

1class DynamicArray<T> {
2 private capacity: number = 4;
3 private length: number = 0;
4 private arr: (T | undefined)[];
5
6 constructor() {
7 this.arr = new Array(this.capacity);
8 }
9
10 push(item: T): void {
11 if (this.length >= thiscapacity

Amortized Analysis

Why is push O(1) amortized even though resize is O(n)?

  • Most operations are O(1) (no resize)
  • Resize happens rarely (when capacity doubles)
  • Cost of resize is spread across many O(1) operations
  • Average cost per operation is O(1)

Multidimensional Arrays

Arrays can have multiple dimensions (2D, 3D, etc.). Common use cases: matrices, grids, images.

1// 2D Array (Matrix)
2const matrix: number[][] = [
3 [1, 2, 3],
4 [4, 5, 6],
5 [7, 8, 9]
6];
7
8// Access element at row 1, column 2
9const value = matrix[1][2]; // 6
10
11// Time: O(1) access
12// Space: O(rows * cols)
13// Edge cases: empty matrix, out of bounds

Time and Space Complexity

OperationStatic ArrayDynamic Array
Access by indexO(1)O(1)
Search (unsorted)O(n)O(n)
Insert at beginningO(n)O(n)
Insert at endN/A (fixed size)O(1) amortized
Insert at middleO(n)O(n)
Delete at beginningO(n)O(n)
Delete at endN/A (fixed size)O(1)
Delete at middleO(n)O(n)
SpaceO(n)O(n)

Arrays vs Other Data Structures

Arrays vs Linked Lists

FeatureArraysLinked Lists
Random AccessO(1)O(n)
Insert at beginningO(n)O(1)
Insert at endO(1) amortizedO(1)
MemoryContiguousScattered
Cache PerformanceExcellentPoor
Memory OverheadLowHigh (pointers)

Choose Arrays when:

  • You need random access
  • You know the size in advance
  • You need cache-friendly access patterns

Choose Linked Lists when:

  • You frequently insert/delete at beginning
  • Size is unknown or changes frequently
  • You don't need random access

Common Pitfalls

  • Out-of-bounds access: Always check bounds before accessing
  • Assuming O(1) insertion: Insertion in middle is O(n), not O(1)
  • Forgetting resize cost: Dynamic array resize is expensive
  • Not pre-allocating: If you know size, pre-allocate to avoid resizes
  • Using wrong data structure: Don't use arrays for frequent insertions/deletions

Examples

Example 1: Two Sum

Find two numbers that add up to target.

1function twoSum(nums: number[], target: number): number[] {
2 const map = new Map<number, number>();
3
4 for (let i = 0; i < nums.length; i++) {
5 const complement = target - nums[i];
6 if (map.has(complement)) {
7 return [map.get(complement) i

Example 2: Maximum Subarray (Kadane's Algorithm)

Find the contiguous subarray with the largest sum.

1function maxSubArray(nums: number[]): number {
2 let maxSum = nums[0];
3 let currentSum = nums[0];
4
5 for (let i = 1; i < nums.length; i++) {
6 currentSum = Math.max(nums[i], currentSum + nums[i]);
7 maxSum = Math.max(maxSum, currentSum);
8 }
9
10 return maxSum;

Interview Questions

Beginner

Q: What is the time complexity of accessing an element in an array by index?

A: O(1) - Arrays provide direct memory access using the base address and index offset. The CPU calculates the memory address as base + index * element_size in constant time.


Intermediate

Q: Explain the difference between static and dynamic arrays. When would you use each?

A:

  • Static arrays have fixed size allocated at compile time. They're faster (no resize overhead) but inflexible. Use when size is known and constant.
  • Dynamic arrays can grow/shrink at runtime. They provide flexibility but have resize costs. Use when size is unknown or changes.

Trade-off: Static arrays are faster but less flexible. Dynamic arrays are flexible but have amortized O(1) insertion cost (occasional O(n) resize).


Senior

Q: How would you implement a dynamic array that minimizes memory waste while maintaining O(1) amortized insertion?

A:

  • Use exponential growth (2x capacity) to amortize resize cost
  • Track both length (used) and capacity (allocated)
  • Only resize when length >= capacity
  • Consider shrink strategy: shrink when length < capacity / 4 to avoid memory waste
  • Use geometric series analysis: resize cost is O(n), but happens 1/n times, so amortized O(1)

Memory optimization: Shrink array when utilization drops below 25% to avoid holding excessive unused memory.


  • Arrays provide O(1) random access but O(n) insertion/deletion in middle
  • Dynamic arrays amortize resize cost to O(1) per insertion
  • Cache locality makes arrays fast for sequential access
  • Choose arrays for random access, linked lists for frequent insertions/deletions
  • Always check bounds before array access
  • Pre-allocate when size is known to avoid resize overhead
  • Two-pointer technique is powerful for array problems
  • Multidimensional arrays are useful for matrices and grids

Key Takeaways

Arrays provide O(1) random access but O(n) insertion/deletion in middle

Dynamic arrays amortize resize cost to O(1) per insertion

Cache locality makes arrays fast for sequential access

Choose arrays for random access, linked lists for frequent insertions/deletions

Always check bounds before array access

Pre-allocate when size is known to avoid resize overhead

Two-pointer technique is powerful for array problems

Multidimensional arrays are useful for matrices and grids


About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.