Arrays, Linked Lists, and Big O, Visualized
Why array access is O(1), what a reference actually is, and how fast O(2ⁿ) gets out of hand. Plus three interactive visualizers to play with.
A lot of data structures content explains things with words when you really need to see memory move. So I built three interactive pages: one for arrays, one for linked lists, and one for Big O. This post is the short version of what each one shows. The links at the bottom are where you actually click around.
Arrays: one unbroken block
An array stores every element in a single contiguous block of memory. No gaps. That one fact explains almost everything about how arrays perform.
- O(1) access. Elements are evenly spaced, so the address of any element is one formula:
base_address + index * element_size. No searching. - Cache-friendly. When you read
arr[0], the CPU pulls nearby memory into cache.arr[1]andarr[2]are right next door, so they're already there. - O(n) insertion. Insert in the middle and every element after it has to shift right.
- Fixed size. To grow, you allocate a bigger block and copy everything. Dynamic arrays like
ArrayListand Python'slisthide this, but it still happens. That's why append is O(1) amortized.
Arrays are also the foundation for most other structures. A stack is an array with a top pointer. A queue can be a circular array with head and tail pointers. A hash map is an array of buckets. A heap is a complete binary tree stored in an array, with children of i at 2i+1 and 2i+2.
The arrays visualizer lets you click any cell and see the address math, then insert and watch every element after it shift.
Linked lists: nodes scattered everywhere
Linked list nodes aren't stored next to each other. They can live anywhere in memory. Each node just holds the address of the next one.
That's the part that confuses people, so here's the key idea: a variable doesn't contain an object. It contains a memory address pointing to where the object lives on the heap. When you write head.next = new Node(3), you're storing the new node's address inside head.next.
class Node {
int val;
Node next;
Node(int val) { this.val = val; }
}
Node head = new Node(5);
head.next = new Node(12);
head.next.next = new Node(8);
Node curr = head;
while (curr != null) {
System.out.println(curr.val);
curr = curr.next;
}
| Operation | Array | Linked list |
|---|---|---|
| Access by index | O(1) | O(n) |
| Insert / delete at head | O(n) | O(1) |
| Insert at end | O(1)* | O(n) |
| Search | O(n) | O(n) |
| Memory | Contiguous | Scattered |
* Amortized, for dynamic arrays. Insert at end is O(n) for a singly linked list with only a head pointer.
Where linked lists show up in real systems: LRU caches (a doubly linked list plus a hashmap gives O(1) access and eviction), undo/redo history, music playlists, and file systems that chain blocks on disk.
Big O: how things scale
Big O is about growth. The cleanest way to feel it is to look at roughly how many operations each class takes at a million elements:
| Complexity | Example | Ops at n = 1M |
|---|---|---|
| O(1) | Array access, hash lookup | 1 |
| O(log n) | Binary search | ~20 |
| O(n) | Single loop | 1,000,000 |
| O(n log n) | Merge sort, heap sort | ~20,000,000 |
| O(n²) | Nested loops, bubble sort | 1,000,000,000,000 |
| O(2ⁿ) | Naive recursive Fibonacci, subsets | Don't even try |
A few things that are worth internalizing:
- Binary search halves the problem every step. A million elements takes about 20 steps.
- O(n log n) is the best you can do for comparison-based sorting.
Arrays.sortand Python'ssort()are both in that class. - Brute-force two sum is O(n²). At 100k elements that's 10 billion operations. A hashmap gets it to O(n).
- Naive recursive Fibonacci branches twice per call. n = 40 takes seconds, n = 50 takes minutes. Memoization brings it to O(n).
# O(2^n): every call branches into two more
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
# Fix: @lru_cache or a dp array gets you O(n)
The Big O page has a slider for n so you can watch the curves pull apart. It makes the difference between n log n and n² obvious in a way the table can't.
Drafted by AI from my notes and guides, reviewed by me.