DSA Notes
A thorough introduction to arrays — what they are, how they work in memory, their properties, all basic operations with time and space complexity, comparison with other structures, and when to use them. The foundation of all data structure knowledge.
Introduction
The array is the most fundamental data structure in computer science. Almost every other data structure is built on top of arrays, implemented using arrays, or compared against arrays. Without a deep understanding of arrays — not just how to use them, but how they work at the memory level — you cannot truly understand any more advanced data structure.
This lesson covers arrays completely: from the raw memory model to Python's dynamic lists, from basic operations to their precise time complexities, from simple 1D arrays to multi-dimensional matrices. Take your time with each section. This is the foundation everything else is built on.
Memory Model — Why Arrays Are Special
Understanding the memory model of arrays reveals *why* they have O(1) random access — the property that makes them so powerful.
The Address Formula
Because all elements are the same size and stored contiguously, the memory address of any element can be calculated directly:
This formula is computed in constant time (a single multiplication and addition). No searching, no traversal — the CPU jumps directly to the correct memory location.
Example with 64-bit (8-byte) doubles:
| Base address | 2000 |
| arr[0] | address 2000 + 0×8 = 2000 |
| arr[2] | address 2000 + 2×8 = 2016 |
| arr[4] | address 2000 + 4×8 = 2032 |
| Access time | O(1) — always one formula calculation |
Why This Makes Random Access O(1)
Accessing arr[999] takes the same time as accessing arr[0]. There is no need to count through 999 elements to find the 999th one — you just compute 2000 + 999×8 = 9992, and the hardware reads from address 9992 directly.
This is fundamentally different from a linked list, where to access the 1000th element, you must follow 999 pointers from the beginning.
Cache Friendliness
Modern processors have a cache hierarchy — small amounts of very fast memory (L1, L2, L3 cache) that buffer recently accessed memory regions. When you access arr[0], the processor loads not just that single value, but an entire cache line (typically 64 bytes = 16 integers) into the fast cache.
When you then access arr[1], arr[2], ..., arr[15], they are already in cache — these accesses are essentially free. This spatial locality makes sequential array traversal dramatically faster in practice than theoretical analysis suggests.
Linked lists, trees, and hash maps with their scattered memory addresses destroy spatial locality, making them much slower in practice despite sometimes having the same theoretical complexity.
Types of Arrays
1. Static Arrays (Fixed-Size)
A static array is allocated with a fixed size that cannot change after creation.
Properties:
- Size must be known at compile time (or at least before allocation)
- Cannot grow or shrink
- Memory allocation is on the stack (for local variables) or heap
- Direct hardware support — the most efficient data structure possible
2. Dynamic Arrays (Automatically Resizing)
A dynamic array grows and shrinks automatically as elements are added or removed. Python's list, Java's ArrayList, and C++'s std::vector are dynamic arrays.
# Python list — dynamic array
scores = [] # Empty, size 0
scores.append(88) # Size becomes 1
scores.append(74) # Size becomes 2
scores.append(91) # Size becomes 3
# Python automatically manages the underlying memoryHow dynamic resizing works (under the hood):
When a Python list runs out of internal capacity:
- A new array with a larger capacity is allocated (typically 1.125× to 2× the current size)
- All existing elements are copied to the new array
- The old array is freed
| Start | capacity = 0 |
| append(88): capacity becomes 4 | arr = [88, _, _, _] |
| append(74) | arr = [88, 74, _, _] |
| append(91) | arr = [88, 74, 91, _] |
| append(65) | arr = [88, 74, 91, 65] (full) |
| append(83): capacity doubles to 8 | copy → arr = [88, 74, 91, 65, 83, _, _, _] |
This copying makes individual appends O(n) sometimes, but amortized over n appends, the average cost per append is O(1).
3. Multi-Dimensional Arrays
Arrays can be extended to multiple dimensions.
2D Array (Matrix):
| Logical | [1][2][3][4] | [5][6][7][8] | [9][10][11][12] |
| In memory | 1 2 3 4 5 6 7 8 9 10 11 12 |
| matrix[1][2] | base + (1×4 + 2) × 4 = base + 24 |
3D Array:
Array Properties — Summary
| Property | Description |
|---|---|
| Element type | All elements same type (in typed languages) |
| Memory layout | Contiguous — no gaps |
| Size | Fixed (static array) or resizable (dynamic array) |
| Indexing | Zero-based in Python, Java, C/C++, JavaScript |
| Access | O(1) — direct address calculation |
| Cache behavior | Excellent spatial locality |
| Memory overhead | Minimal — just the elements (+ small header) |
Basic Array Operations
1. Creation and Initialization
2. Access — O(1)
3. Update — O(1)
4. Traversal — O(n)
5. Search — O(n) for unsorted, O(log n) for sorted
6. Insertion — O(1) at end, O(n) at position
Why insert at position is O(n):
| Before | [10, 25, 8, 42, 15] |
| Step 1 | Shift 15 right: [10, 25, 8, 42, _, 15] |
| Step 2 | Shift 42 right: [10, 25, 8, _, 42, 15] |
| Step 3 | Shift 8 right: [10, 25, _, 8, 42, 15] |
| Step 4 | Place 77: [10, 25, 77, 8, 42, 15] |
| Worst case (insert at beginning): n shifts | O(n) |
7. Deletion — O(1) at end, O(n) at position
Why delete from position is O(n):
| Before | [10, 25, 8, 42, 15] |
| Step 1 | Move 42 left: [10, 25, 42, _, 15] |
| Step 2 | Move 15 left: [10, 25, 42, 15, _] |
| Step 3 | Reduce size: [10, 25, 42, 15] |
| Worst case (delete first element): n-1 shifts | O(n) |
Operations Complexity Summary
| Operation | Time Complexity | Space | Notes |
|---|---|---|---|
| Access arr[i] | O(1) | O(1) | Direct address formula |
| Update arr[i] = v | O(1) | O(1) | Direct address formula |
| Append at end | O(1)* | O(1) | *Amortized (O(n) rarely) |
| Insert at position i | O(n) | O(1) | Shifts n-i elements right |
| Delete from end | O(1) | O(1) | |
| Delete from position i | O(n) | O(1) | Shifts n-i-1 elements left |
| Search (unsorted) | O(n) | O(1) | Linear scan |
| Search (sorted) | O(log n) | O(1) | Binary search |
| Sort | O(n log n) | O(log n) | Typical efficient sort |
| Traverse (all elements) | O(n) | O(1) | Must visit each once |
Common Array Operations in Python
Arrays vs. Other Data Structures
| Feature | Array | Linked List | Hash Map | BST |
|---|---|---|---|---|
| Access by index | O(1) ✅ | O(n) ❌ | N/A | N/A |
| Access by key | O(n) | O(n) | O(1) ✅ | O(log n) ✅ |
| Insert at end | O(1)* ✅ | O(1) ✅ | O(1)* ✅ | O(log n) |
| Insert at front | O(n) ❌ | O(1) ✅ | N/A | O(log n) |
| Insert at position | O(n) | O(1)† ✅ | N/A | O(log n) |
| Delete from end | O(1) ✅ | O(n) ❌ (or O(1) with tail ptr) | — | — |
| Delete arbitrary | O(n) | O(1)† ✅ | O(1)* ✅ | O(log n) |
| Memory overhead | Low ✅ | High (pointers) | Medium | Medium |
| Cache performance | Excellent ✅ | Poor ❌ | Poor ❌ | Poor ❌ |
| Maintains order | Yes | Yes | No | Yes (sorted) |
*Amortized †Given a pointer to the node
Choose arrays when:
- You need fast random access by index
- You traverse elements sequentially
- Memory efficiency is important
- The number of elements is roughly known in advance
- Cache performance matters
Choose linked lists when:
- You frequently insert/delete at arbitrary positions
- The size varies widely and unpredictably
- You do not need random access
Real-World Applications of Arrays
1. Image Processing
2. Implementing Other Data Structures
3. Numerical Computation
4. Sorting and Searching
Arrays are the natural structure for:
- Sorting algorithms (Merge Sort, Quick Sort, Heap Sort all operate on arrays)
- Binary search (requires sorted array with O(1) random access)
- Sliding window technique (fast subarray queries)
- Two-pointer technique (fast pair finding)
- Prefix sum arrays (fast range sum queries)
Common Mistakes and Pitfalls
Off-by-One Errors
Modifying Array While Iterating
Shallow Copy vs. Deep Copy
Summary
Arrays are the bedrock of data structures:
- Definition: Collection of same-type elements in contiguous memory, accessed by index
- Memory model: Direct address formula → O(1) random access → the most important property
- Cache friendly: Spatial locality makes sequential access very fast in practice
- Types: Static (fixed size), Dynamic (resizable — Python list), Multi-dimensional
- Key complexities: Access O(1), search O(n)/O(log n), append O(1), insert/delete at position O(n)
- Best for: Random access, sequential traversal, implementing stacks/queues, numerical computation
- Not best for: Frequent insertions/deletions in the middle (use linked list), fast arbitrary search (use hash map)
Every data structure you study next — linked lists, stacks, queues, heaps, hash tables — will either use arrays internally or will be compared against arrays. The understanding you build here carries forward through your entire DSA journey.
*Next Lesson: Array Operations — Deep dive into every array operation with detailed implementation, edge cases, and optimization techniques.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Arrays — The Complete Introduction to the Most Fundamental Data Structure.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, arrays, introduction, arrays — the complete introduction to the most fundamental data structure
Related Data Structures & Algorithms Topics