Core Data Structures
How arrays, linked lists, hash maps, trees, graphs, and other core data structures work and where they are used
Contents
How a backend system behaves comes down to which data structure was chosen.
Overview
A data structure is a scheme for storing and accessing data efficiently. For a backend engineer, data structures are the key to understanding database indexes, cache systems, and how distributed systems behave. Why Redis is fast, how PostgreSQL manages indexes, how a distributed database keeps data consistent -- all of it starts with data structures.
Arrays and linked lists
Time complexity by operation
| Operation | Array | Linked list |
|---|---|---|
| Index access | O(1) | O(n) |
| Insert (front) | O(n) | O(1) |
| Insert (back) | O(1) amortized | O(1) (with a tail pointer) |
| Insert (middle) | O(n) | O(1) (when the position is known) |
| Delete | O(n) | O(1) (when the position is known) |
| Search | O(n) | O(n) |
Cache efficiency
Array (Cache-friendly):
Memory: [1][2][3][4][5][6][7][8] ← contiguous layout
1 cache line loaded → 8 elements reachable
Linked list (Cache-unfriendly):
Memory: [Node1]...[Node5]...[Node2]...[Node7]...
Nodes scattered all over memory → frequent cache missesWhat this means in practice: by Big-O alone, insertion and deletion in a linked list are O(1), but in real performance the cost of cache misses dominates. Cache locality, covered in the memory management notes, is what decides actual throughput.
Backend use cases
- Arrays: JSON arrays, Redis lists (internally quicklist plus listpack), contiguous log buffers
- Linked lists: the doubly linked list inside an LRU cache, OS process queues, free lists in memory allocators
Hash tables
Core idea
A key is passed through a hash function to produce an array index, so data can be stored and looked up in O(1) time.
Key: "user:1001"
→ hash("user:1001") = 7
→ table[7] = {name: "Kim", age: 30}
┌───┬───┬───┬───┬───┬───┬───┬────────────────┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7: user:1001 │ 8 │
└───┴───┴───┴───┴───┴───┴───┴────────────────┴───┘Collision resolution
Chaining
Several entries at the same index are stored in a linked list:
table[3] → [key:A, val:1] → [key:B, val:2] → [key:C, val:3]- Upside: simple to implement, works even when the load factor exceeds 1
- Downside: extra memory for pointers, poor cache behavior
Open addressing
On collision, probe for another empty slot:
| Scheme | Probe order | Upside | Downside |
|---|---|---|---|
| Linear probing | h+1, h+2, h+3... | Good cache behavior | Clustering |
| Quadratic probing | h+1, h+4, h+9... | Less primary clustering | Secondary clustering |
| Double hashing | h+i*h2(key) | Even distribution | Computation cost |
Robin Hood hashing
A variant of open addressing in which an element with a long probe distance displaces one with a shorter distance:
- The Rust standard library HashMap used it in the past, but switched to a SwissTable (hashbrown) design in 1.36
- Reducing the variance of probe distances improves worst-case performance
Where hash tables show up
| System | How it is used |
|---|---|
| Redis | The entire key-value store is a hash table |
| DB indexes | Hash indexes (optimal for equality comparisons) |
| Caches | Memcached, CDN cache routing |
| Programming languages | Python dict, Java HashMap, Go map |
| DNS | Domain-to-IP mapping cache |
Tree structures
BST (Binary Search Tree)
A binary tree that follows the rule left child < parent < right child:
8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13| Operation | Average | Worst (skewed) |
|---|---|---|
| Search | O(log n) | O(n) |
| Insert | O(log n) | O(n) |
| Delete | O(log n) | O(n) |
Self-balancing trees
AVL Tree
- At every node, the height difference between the left and right subtrees is at most 1
- Strict balance means fast search, at the cost of rotations on insert and delete
- Used in: in-memory indexes, dictionary structures
Red-Black Tree
- Each node is either red or black
- Rules: the root is black, the children of a red node are black, every path holds the same number of black nodes
- Looser balance than AVL, so fewer rotations on insert and delete
- Used in: the Linux CFS scheduler (process management and CPU scheduling), Java TreeMap, C++ std::map
B-Tree and B+Tree
B-Tree
- A multiway search tree: one node holds several keys
- Optimized for disk I/O: node size is matched to a disk block (4KB to 16KB)
- A B-Tree of order m: each node holds at most m-1 keys and m children
B-Tree (order 4):
[10 | 20 | 30]
/ | | \
[3|5|7] [12|15] [22|25] [35|40|45]B+Tree -- the standard for DB indexes
A B-Tree variant that is the de facto standard for database indexes:
B+Tree:
Internal nodes: keys only (routing)
[10 | 20 | 30]
/ | | \
Leaf nodes: keys + data, chained as a linked list
[3|5|7] ←→ [10|12|15] ←→ [20|22|25] ←→ [30|35|40]| B-Tree | B+Tree |
|---|---|
| Data in every node | Data only in leaf nodes |
| Inefficient range scans | Efficient range scans via the leaf linked list |
| Larger internal nodes | More keys per internal node, so higher fan-out |
Why is a B+Tree optimal for a database?
- High fan-out means a shallower tree, which minimizes disk I/O
- The leaf linked list makes range queries (WHERE age BETWEEN 20 AND 30) efficient
- Small internal nodes mean more of them fit in cache
PostgreSQL, MySQL InnoDB, and SQLite all use B+Tree-based indexes.
Heaps
Definition
A complete binary tree plus the heap property (parent >= child for a max heap, parent <= child for a min heap).
Min Heap:
1
/ \
3 5
/ \ / \
7 9 8 10Operation complexity
| Operation | Time complexity |
|---|---|
| Read the min/max | O(1) |
| Insert | O(log n) |
| Delete the min/max | O(log n) |
| Build the heap (heapify) | O(n) |
Backend applications
| Use case | Heap type | Description |
|---|---|---|
| Priority queue | Min/Max Heap | Job scheduling, timer management |
| CPU scheduling | Min Heap | Picking the next process to run |
| Top-K problems | Min Heap of size K | Keeping the top K items |
| Dijkstra's algorithm | Min Heap | Selecting the lowest-cost vertex on a shortest path |
| Event simulation | Min Heap | Processing the nearest future event |
Graphs
Representations
Adjacency Matrix
A B C D
A [ 0 1 1 0 ]
B [ 1 0 0 1 ]
C [ 1 0 0 1 ]
D [ 0 1 1 0 ]- Space: O(V^2)
- Edge lookup: O(1) --
matrix[u][v] - Suits dense graphs
Adjacency List
A → [B, C]
B → [A, D]
C → [A, D]
D → [B, C]- Space: O(V + E)
- Edge lookup: O(degree(v))
- Suits sparse graphs -- which is most real-world graphs
Graphs in the backend
| Use case | Algorithm |
|---|---|
| Social networks (friend suggestions) | BFS, common neighbors |
| Routing (network paths) | Dijkstra, Bellman-Ford |
| Dependency resolution (build systems) | Topological sort |
| Recommendation systems | Graph neural networks (GNN) |
| Microservice dependencies | DAG (Directed Acyclic Graph) |
LSM-Tree
Core concept
A write-optimized data structure. Where a B+Tree is read-optimized, an LSM-Tree is designed for high write throughput.
How it works
Components
| Component | Role |
|---|---|
| WAL | Records the write in a log first (for crash recovery) |
| MemTable | An in-memory sorted structure (Red-Black Tree or Skip List) |
| SSTable (Sorted String Table) | A sorted immutable file on disk |
| Compaction | Merges several SSTables, dropping duplicates and applying deletes |
The read path
Read request → check MemTable → check L0 SSTable → L1 → L2 → ...
(fast) (Bloom Filter rules out misses fast)B+Tree vs LSM-Tree
| Property | B+Tree | LSM-Tree |
|---|---|---|
| Write performance | Medium (in-place updates) | High (sequential writes) |
| Read performance | High (single lookup) | Medium (several levels checked) |
| Write amplification | Low to medium | High (compaction) |
| Space amplification | Low | High (temporary duplicates) |
| Suitable workload | Read-heavy (OLTP) | Write-heavy |
Systems that use it
| System | Purpose |
|---|---|
| RocksDB (Meta) | General-purpose embedded KV store, MySQL MyRocks |
| LevelDB (Google) | Lightweight KV store |
| Cassandra | Distributed NoSQL database |
| HBase | Hadoop-based distributed database |
| TiKV | The storage engine behind TiDB |
| CockroachDB | Distributed SQL database |
Recent optimization directions
- SpanDB (FAST 2021): exploits NVMe SSD speed by placing the WAL and the upper LSM levels on the fast SSD
- Dynamic scheduling: uses NVMe protocol characteristics to run flushes and compactions without interfering with client requests
- Tiered Compaction: groups SSTables by tier instead of compacting level by level, reducing write amplification
Bloom Filter
Concept
A data structure that answers whether an element belongs to a set probabilistically:
- If it answers "absent", the element is definitely absent (no false negatives)
- If it answers "present", the element may or may not be there (false positives are possible)
Structure
Bit array: [0][0][0][0][0][0][0][0][0][0]
↑ ↑ ↑
hash1("key") hash2("key") hash3("key")
After insert: [0][1][0][0][1][0][1][0][0][0]
Lookup: if all 3 hashed positions are 1, "probably present"
if any one is 0, "definitely absent"False positive probability
With a bit array of size m, k hash functions, and n inserted elements:
P(false positive) ≈ (1 - e^(-kn/m))^kBackend applications
| System | How it is used |
|---|---|
| LSM-Tree (RocksDB) | Fast check for whether a key exists in an SSTable → avoids needless disk reads |
| Cassandra | Quick check for partition key existence |
| Redis | Large-scale duplicate detection via the RedisBloom module |
| CDN | Fast cache hit/miss decisions |
| Web crawlers | Whether a URL was visited (across billions of URLs) |
Skip List
Concept
A sorted linked list with several levels of express lanes added on top, giving O(log n) search:
Level 3: HEAD ────────────────────────────→ 50 ─────→ NIL
Level 2: HEAD ──────→ 20 ────────────────→ 50 ─────→ NIL
Level 1: HEAD ──→ 10 → 20 ──→ 30 ──────→ 50 → 60 → NIL
Level 0: HEAD → 5 → 10 → 20 → 25 → 30 → 50 → 55 → 60 → NILTime complexity
| Operation | Average | Worst |
|---|---|---|
| Search | O(log n) | O(n) |
| Insert | O(log n) | O(n) |
| Delete | O(log n) | O(n) |
Advantages over a B+Tree
- Simpler to implement (no rotations)
- Lock-free implementations are possible, which helps under concurrency
- This is why the Redis Sorted Set (ZSET) uses a Skip List
CRDT
The problem: conflicts in distributed systems
When several nodes modify the same data at once, conflicts arise. A CRDT is a data structure in which conflicts are mathematically impossible.
Core idea
Every edit operation is expressed as a commutative operation:
Node A: counter += 1 (A: 1)
Node B: counter += 3 (B: 3)
Merge: A + B = 1 + 3 = 4 (order does not matter)CRDT types
| Type | Description | Example |
|---|---|---|
| G-Counter | Increment-only counter | Page view counter |
| PN-Counter | Increment/decrement counter (two G-Counters) | Likes and dislikes |
| G-Set | Add-only set | User visit history |
| OR-Set (Observed-Remove Set) | Set supporting add and remove | Shopping cart |
| LWW-Register (Last Writer Wins) | The latest write wins | User profile |
| RGA (Replicated Growable Array) | Ordered list | Text in a collaborative editor |
Real-world use
| System | CRDT usage |
|---|---|
| Redis Enterprise | CRDT-based conflict resolution in active-active geo replication |
| Riak | Native support for counter, set, and map CRDTs |
| Azure Cosmos DB | Applies CRDT principles to multi-region writes |
| Figma | Real-time collaborative design editing |
| League of Legends | Riak CRDTs handling chat for 7.5 million concurrent users (Riot Games engineering blog) |
Relationship to the CAP theorem
CAP theorem: only 2 of Consistency, Availability, Partition Tolerance can be chosen
In an AP (Availability + Partition Tolerance) system, a CRDT gives a
mathematical guarantee of Eventual Consistency.
→ Consensus algorithms such as Raft/Paxos are the answer for CP (Consistency + Partition Tolerance) systemsProbabilistic data structures
HyperLogLog -- estimating distinct counts
Estimates cardinality over a large dataset using very little memory:
| Approach | Memory | Accuracy |
|---|---|---|
| HashSet | O(n) -- scales with the data | 100% |
| HyperLogLog | ~12KB (fixed) | ~0.81% error rate |
# Redis HyperLogLog
PFADD visitors "user1" "user2" "user3"
PFCOUNT visitors
# (integer) 3
# Even a billion entries still use only ~12KBCount-Min Sketch -- frequency estimation
Approximates the occurrence frequency of an item in streaming data:
- Returns a value at least as large as the true frequency (overestimates are possible, underestimates are not)
- Used for: network traffic analysis, hot item detection, anomaly detection
Cuckoo Filter
An improvement on the Bloom Filter:
- Supports deletion (a Bloom Filter cannot delete)
- Better space efficiency at high load factors
- Used for: duplicate detection, network packet filtering
Summary
The data structure a system relies on is the visible result of matching a structure to a workload. The table below lists which structure sits at the core of several representative systems.
| System | Core data structure | Reason |
|---|---|---|
| PostgreSQL | B+Tree | Efficient range queries, minimal disk I/O |
| Redis | Hash Table, Skip List, ziplist | O(1) lookup, sorted sets |
| RocksDB | LSM-Tree, Bloom Filter | Write optimization, avoiding needless reads |
| Elasticsearch | Inverted Index | Optimized full-text search |
| Kafka | Ring buffer, segmented log | Sequential I/O, high throughput |
| Distributed DBs | CRDT, Consistent Hash | Data consistency, even distribution |
Read-heavy workloads use a B+Tree to cut disk seeks, and write-heavy workloads pick an LSM-Tree for sequential writes. In-memory lookups belong to hash tables, while sorted range queries belong to skip lists or trees. In some workloads accuracy can be traded away, as in large-scale membership tests and cardinality estimation.
There, probabilistic structures such as Bloom Filter and HyperLogLog save a great deal of memory. In a distributed setting, CRDTs handle conflicts in AP systems and consensus algorithms handle consistency in CP systems. Choosing a data structure is ultimately a trade-off about which operations get to be fast and what gets given up.