Shawn Ng

Shawn Ng

4 Data Structures Every Developer Should Master

  • data-structures
  • algorithms
  • software-engineering
  • computer-science

You do not need to memorize every data structure in existence. But there are four that show up so often in real-world code that mastering them will make you a dramatically better developer. In this post, we will break down arrays, hash tables, stacks and queues, and trees, covering when to reach for each, their time complexity trade-offs, and practical applications.

Check out the full video on my YouTube channel Divide and Quantum.

Why These Four?

Every complex data structure is built on top of simpler ones. Databases use B-trees. Caches use hash tables. Undo systems use stacks. If you deeply understand these four primitives, you will be able to reason about almost any system you encounter.

Arrays: The Foundation

An array is a contiguous block of memory where elements are stored sequentially. This simple layout gives arrays a superpower: constant-time random access. If you know the index, you can jump directly to that element.

Time Complexity

Operation Average Case
Access by index O(1)
Search (unsorted) O(n)
Insert at end O(1) amortized
Insert at beginning O(n)
Delete by index O(n)

When to Use Arrays

Arrays are your default choice when you need ordered data and frequent random access. Think of leaderboards, time-series data, or any list you iterate over from start to finish.

python
# Arrays shine when you need fast index-based access scores = [95, 82, 91, 78, 88] # O(1) access - jump straight to the third score third_score = scores[2] # 91 # Appending is fast (amortized O(1) in Python lists) scores.append(73) # But inserting at the start is expensive - every element shifts scores.insert(0, 99) # O(n) operation

The Hidden Advantage: Cache Friendliness

Because array elements sit next to each other in memory, CPUs can load them into cache lines efficiently. This means iterating over an array is significantly faster in practice than iterating over a linked list, even though both are O(n) in theory. The constant factor matters.

Hash Tables: O(1) Lookup Power

A hash table maps keys to values using a hash function. Python dictionaries, JavaScript objects, and Java HashMaps are all hash tables under the hood.

Time Complexity

Operation Average Case Worst Case
Lookup O(1) O(n)
Insert O(1) O(n)
Delete O(1) O(n)

The worst case happens when every key collides into the same bucket, but with a good hash function, this is extremely rare.

When to Use Hash Tables

Reach for a hash table when you need fast lookups by key, when counting occurrences, or when you need to detect duplicates.

python
# Counting word frequencies - classic hash table use case def word_frequency(text): freq = {} for word in text.lower().split(): freq[word] = freq.get(word, 0) + 1 return freq text = "the cat sat on the mat the cat" print(word_frequency(text)) # {'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1} # Two-sum problem: find two numbers that add up to a target def two_sum(nums, target): seen = {} for i, num in enumerate(nums): complement = target - num if complement in seen: # O(1) lookup return [seen[complement], i] seen[num] = i return [] print(two_sum([2, 7, 11, 15], 9)) # [0, 1]

The two-sum example perfectly illustrates the hash table advantage. A brute-force approach checks every pair in O(n^2). With a hash table, you solve it in a single O(n) pass.

Stacks and Queues: Order Matters

Stacks and queues are both linear data structures, but they differ in one crucial way: the order elements come out.

  • Stack (LIFO): Last In, First Out. Think of a stack of plates.
  • Queue (FIFO): First In, First Out. Think of a line at a coffee shop.

Time Complexity (Both)

Operation Time
Push / Enqueue O(1)
Pop / Dequeue O(1)
Peek O(1)

Stack in Action: Balanced Parentheses

One of the most common interview questions and a genuinely useful real-world pattern.

python
def is_balanced(expression): stack = [] matching = {')': '(', ']': '[', '}': '{'} for char in expression: if char in '([{': stack.append(char) elif char in ')]}': if not stack or stack[-1] != matching[char]: return False stack.pop() return len(stack) == 0 print(is_balanced("{[()]}")) # True print(is_balanced("{[(])}")) # False print(is_balanced("((()")) # False

Queue in Action: Breadth-First Search

Queues are essential for BFS, task scheduling, and message processing systems like Kafka and RabbitMQ.

python
from collections import deque def bfs(graph, start): visited = set() queue = deque([start]) order = [] while queue: node = queue.popleft() # O(1) with deque if node not in visited: visited.add(node) order.append(node) for neighbor in graph[node]: if neighbor not in visited: queue.append(neighbor) return order graph = { 'A': ['B', 'C'], 'B': ['D', 'E'], 'C': ['F'], 'D': [], 'E': [], 'F': [] } print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E', 'F']

Note the use of collections.deque instead of a regular list. A list's pop(0) is O(n) because it shifts every remaining element. A deque gives you O(1) pops from both ends.

Trees: Hierarchical Data Done Right

Trees are non-linear data structures where each node has zero or more children. The most practical variant for developers is the binary search tree (BST), where the left child is always smaller and the right child is always larger.

Time Complexity (Balanced BST)

Operation Average Case Worst Case (unbalanced)
Search O(log n) O(n)
Insert O(log n) O(n)
Delete O(log n) O(n)

The worst case happens when the tree degenerates into a linked list, which is why self-balancing trees like AVL trees and Red-Black trees exist.

Building a Simple BST

python
class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None class BST: def __init__(self): self.root = None def insert(self, val): if not self.root: self.root = TreeNode(val) return self._insert(self.root, val) def _insert(self, node, val): if val < node.val: if node.left is None: node.left = TreeNode(val) else: self._insert(node.left, val) else: if node.right is None: node.right = TreeNode(val) else: self._insert(node.right, val) def search(self, val): return self._search(self.root, val) def _search(self, node, val): if node is None: return False if val == node.val: return True elif val < node.val: return self._search(node.left, val) else: return self._search(node.right, val) tree = BST() for val in [8, 3, 10, 1, 6, 14]: tree.insert(val) print(tree.search(6)) # True print(tree.search(7)) # False

Where Trees Show Up

Trees are everywhere once you start looking. File systems are trees. The DOM in your browser is a tree. Database indexes use B-trees, a self-balancing variant that minimizes disk reads. Compilers parse your code into abstract syntax trees. Even your router uses a trie, a specialized tree, to match URL patterns.

Choosing the Right Data Structure

Here is a quick decision framework:

  • Need fast access by position? Use an array.
  • Need fast access by key? Use a hash table.
  • Need to process items in a specific order (LIFO/FIFO)? Use a stack or queue.
  • Need sorted data with fast insert, delete, and search? Use a tree.

The best developers do not just know these data structures exist. They develop an intuition for which one fits the problem at hand. That intuition comes from practice, and it starts with understanding the fundamentals covered here.

Subscribe on YouTube