Topic Overview
Arrays
Master arrays: the most fundamental data structure. Learn operations, time complexity, dynamic arrays, and when to use arrays vs other structures.
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];23// Access element at index 24const value = arr[2]; // 3056// Time: O(1) - direct memory access7// 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];23// Update element at index 24arr[2] = 35; // [10, 20, 35, 40, 50]56// 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 beginning2function insertAtBeginning(arr: number[], value: number): number[] {3 arr.unshift(value); // O(n) - shifts all elements4 return arr;5}67// Insert at end8function insertAtEnd(arr: number[], value: number): number[] {9 arr.push(value); // O(1) amortized for dynamic arrays10 return arr;11}1213// Insert at middle
Delete
Remove an element from a specific position. Requires shifting elements.
1// Delete at beginning2function deleteAtBeginning(arr: number[]): number[] {3 arr.shift(); // O(n) - shifts all elements4 return arr;5}67// Delete at end8function deleteAtEnd(arr: number[]): number[] {9 arr.pop(); // O(1) for dynamic arrays10 return arr;11}1213// Delete at middle14function deleteAtMiddle(arr: number[ index
Search
Find the index of an element (linear search).
1// Linear search2function 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 found9}1011// Time: O(n) - worst case checks all elements12// 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
- Initial Capacity: Start with a small array (e.g., capacity 4)
- Growth Strategy: When full, allocate a larger array (usually 2x current size)
- Copy Elements: Copy all elements from old array to new array
- 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)[];56 constructor() {7 this.arr = new Array(this.capacity);8 }910 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];78// Access element at row 1, column 29const value = matrix[1][2]; // 61011// Time: O(1) access12// Space: O(rows * cols)13// Edge cases: empty matrix, out of bounds
Time and Space Complexity
| Operation | Static Array | Dynamic Array |
|---|---|---|
| Access by index | O(1) | O(1) |
| Search (unsorted) | O(n) | O(n) |
| Insert at beginning | O(n) | O(n) |
| Insert at end | N/A (fixed size) | O(1) amortized |
| Insert at middle | O(n) | O(n) |
| Delete at beginning | O(n) | O(n) |
| Delete at end | N/A (fixed size) | O(1) |
| Delete at middle | O(n) | O(n) |
| Space | O(n) | O(n) |
Arrays vs Other Data Structures
Arrays vs Linked Lists
| Feature | Arrays | Linked Lists |
|---|---|---|
| Random Access | O(1) | O(n) |
| Insert at beginning | O(n) | O(1) |
| Insert at end | O(1) amortized | O(1) |
| Memory | Contiguous | Scattered |
| Cache Performance | Excellent | Poor |
| Memory Overhead | Low | High (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>();34 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];45 for (let i = 1; i < nums.length; i++) {6 currentSum = Math.max(nums[i], currentSum + nums[i]);7 maxSum = Math.max(maxSum, currentSum);8 }910 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) andcapacity(allocated) - Only resize when
length >= capacity - Consider shrink strategy: shrink when
length < capacity / 4to 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
- Linked Lists - Alternative to arrays for dynamic insertion
- Hash Tables - Fast lookup alternative to arrays
- Time & Space Complexity - Understanding array operation costs
- Searching Algorithms - Binary search on sorted arrays
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
What's next?