Master the building blocks of data structures and algorithms: understand time complexity, implement essential data structures like stacks, queues, and hash maps, then conquer the most important sorting algorithms and learn how to customize them for any use case.
Every great programmer stands on a foundation of data structures and algorithms. Before you can solve complex problems, you need to understand how data is organized, how operations perform at scale, and which tools to reach for in different situations.
This chapter starts with Big O notation --- the universal language for reasoning about efficiency. You will learn to look at code and immediately estimate whether it will run in milliseconds or hours. Then you will build the fundamental data structures that appear in nearly every technical interview and production codebase: stacks, queues, and hash maps.
The second half focuses on sorting, one of the most studied problems in computer science. You will implement the simple quadratic sorts to understand the basics, then level up to merge sort and quick sort --- the divide-and-conquer algorithms that power real-world systems. Finally, you will learn to write custom comparators so you can sort anything by any criteria.
This chapter covers:
Click any topic to jump in
The mental model: choosing how to organize data determines which operations are cheap. Big O lets you compare choices before you code.
Stacks and queues are the two simplest linear structures built on arrays or linked lists.
Last-in-first-out storage that mirrors the call stack and solves matching problems like valid parentheses.
First-in-first-out storage that powers BFS, level-order traversal, and rate limiting via sliding windows.
Average-case $O(1)$ lookups via hashing — the workhorse of counting, deduping, and complement lookups.
Once you can store and retrieve, the next question is how to order data efficiently.
Bubble, selection, insertion — the $O(n^2)$ baseline. Understanding stability matters more than raw speed here.
Divide and conquer sorts that reach $O(n \log n)$ — merge sort is stable, quick sort is faster in practice.
Sorting by arbitrary criteria — key functions, multi-key, and the strict-weak-ordering contract.
Data structures are specialized formats for organizing and storing data so that operations like searching, inserting, and deleting can be performed efficiently. Choosing the right data structure can mean the difference between a program that runs in seconds and one that takes hours.
Big O notation provides a way to describe how an algorithm's runtime grows as the input size increases. It strips away constants and lower-order terms to focus on the dominant factor.
A data structure is a way of organizing data in memory so that particular operations can be performed efficiently. Different data structures excel at different operations: arrays provide O(1) random access, linked lists provide O(1) insertion at the head, hash maps provide O(1) average-case lookups. Choosing the right data structure is the first and most important algorithmic decision you make.
A data structure is a triple where is the set of states, are the supported operations, and is the cost model. Choosing a structure means minimising where is how often each operation is performed. For a workload with lookups and inserts, a hash set costs while a sorted array costs — the right choice can change the total by a polynomial factor.
Compare the time complexity of finding an element in an unsorted list vs. a sorted list vs. a hash set. Which would you choose if you need to check membership millions of times?
Big O notation describes the upper bound of an algorithm's growth rate. The most common complexities, from fastest to slowest: O(1) constant --- hash map lookup, O(log n) logarithmic --- binary search, O(n) linear --- scanning an array, O(n log n) linearithmic --- merge sort, O(n^2) quadratic --- nested loops. When analyzing code, count the number of times the innermost operation executes as a function of n.
iff there exist constants and such that for all . This definition ignores constants and lower-order terms because for any polynomial , the dominant term eventually dwarfs the rest: . Big O is an upper bound on growth, giving a machine-independent way to compare algorithms.
What is the time complexity of the following code?
for i in range(n):
for j in range(n):
print(i, j)
Space complexity measures how much additional memory an algorithm uses relative to input size. An in-place algorithm like insertion sort uses O(1) extra space, while merge sort requires O(n) extra space for the temporary arrays. When memory is constrained, space complexity matters as much as time complexity.
Space complexity counts the extra memory used as a function of input size , excluding the input itself. For a recursive algorithm with recursion depth and local variables of size , space is because the call stack holds frames simultaneously. Example: merge sort uses auxiliary space for the merge buffer plus for recursion, totalling .
What is the space complexity of creating a new list that contains the squares of every element in the input list?