A Big O cheat sheet is a reference table of the time and space complexity of common data structure operations and algorithms, used to choose and justify solutions in coding interviews. Big O notation describes how runtime or memory grows as input size n grows, keeping only the dominant term and dropping constants. This page gives you the full tables plus a five-step method to derive the complexity of any solution you write.
Key Takeaways
- Most accepted interview solutions are O(n) or O(n log n). Read the input constraints first, because they tell you which class the interviewer expects.
- Hash map operations are O(1) on average and O(n) in the worst case. Say "average" out loud; interviewers notice.
- A nested loop is not automatically O(n squared). If the inner pointer never moves backward, the total work is often O(n), as in sliding window.
- Recursion costs space. A recursive DFS uses O(h) stack space, where h is the recursion depth, even with no extra data structures.
- Amortized O(1) (dynamic array append) is a worst-case guarantee across a sequence, not a probability statement.
- To derive complexity fast: count the work per element, multiply by how many times each element is touched, then add the cost of every library call you used.
What Is Big O Notation?
Big O notation is an upper bound on how an algorithm's cost grows as its input grows. If an algorithm does 3n + 50 steps, its complexity is O(n), because for large n the constant 50 and the multiplier 3 stop mattering.
Textbooks separate Big O (upper bound), Big Omega (lower bound), and Big Theta (tight bound). In interviews, "Big O" usually means the tight worst-case bound.
Two rules cover most simplification:
- Drop constants. O(2n) is O(n). O(n/2) is O(n).
- Keep the dominant term. O(n squared + n) is O(n squared). O(n + m) stays as is when n and m are independent inputs.
Common Big O Complexity Classes
These are the classes you will meet in interviews, ordered from fastest to slowest growth. The operation counts for n = 1,000,000 show why the class matters more than micro-optimizations.
| Class | Name | Typical source | Ops at n = 10^6 (approx.) |
|---|---|---|---|
| O(1) | Constant | Array index, hash map get | 1 |
| O(log n) | Logarithmic | Binary search, balanced BST, heap push | 20 |
| O(n) | Linear | Single pass, hash map counting | 1,000,000 |
| O(n log n) | Linearithmic | Sorting, heap of n items | 20,000,000 |
| O(n^2) | Quadratic | All pairs, naive nested loops | 10^12 |
| O(n^3) | Cubic | Triple loops, Floyd-Warshall | 10^18 |
| O(2^n) | Exponential | All subsets, naive recursion | Infeasible past n of about 30 |
| O(n!) | Factorial | All permutations | Infeasible past n of about 12 |
Use constraints to predict the target complexity
A widely used rule of thumb is that judges and interview runtimes handle roughly 10^7 to 10^8 simple operations per second. It is an approximation, not a spec, but it turns the constraint line into a hint.
| Constraint on n | Complexity that usually fits |
|---|---|
| n up to 10 | O(n!) or O(n * 2^n) |
| n up to 20 | O(2^n), bitmask DP |
| n up to 500 | O(n^3) |
| n up to 5,000 | O(n^2) |
| n up to 10^5 or 10^6 | O(n log n) or O(n) |
| n above 10^7, or values up to 10^9 | O(n), O(log n), or math |
If the problem says n is up to 10^5 and your idea is O(n squared), you need a different idea before you write code. Our coding interview patterns cheat sheet maps those signals to specific techniques.
Data Structure Operations Table
This is the core table. Times are worst case unless marked "avg" (average) or "amort" (amortized). n is the number of elements, L is key or word length.
| Data structure | Access | Search | Insert | Delete | Space |
|---|---|---|---|---|---|
| Array (static) | O(1) | O(n) | O(n) | O(n) | O(n) |
| Dynamic array (list, ArrayList, vector) | O(1) | O(n) | O(1) amort at end, O(n) middle | O(1) at end, O(n) middle | O(n) |
| Singly linked list | O(n) | O(n) | O(1) at head | O(1) at head, O(n) by value | O(n) |
| Doubly linked list | O(n) | O(n) | O(1) with node reference | O(1) with node reference | O(n) |
| Stack | O(n) | O(n) | O(1) push | O(1) pop | O(n) |
| Queue / deque | O(n) | O(n) | O(1) at ends | O(1) at ends | O(n) |
| Hash map / hash set | N/A | O(1) avg, O(n) worst | O(1) avg | O(1) avg | O(n) |
| Binary search tree (unbalanced) | O(n) | O(n) | O(n) | O(n) | O(n) |
| Balanced BST (red-black, AVL) | O(log n) | O(log n) | O(log n) | O(log n) | O(n) |
| Binary heap | O(1) peek min/max | O(n) | O(log n) | O(log n) pop | O(n) |
| Trie | N/A | O(L) | O(L) | O(L) | O(total characters) |
| Union-find (path compression + rank) | N/A | O(α(n)) amort | O(1) | N/A | O(n) |
| Graph, adjacency list | N/A | O(degree) edge check | O(1) add edge | O(degree) | O(V + E) |
| Graph, adjacency matrix | N/A | O(1) edge check | O(1) | O(1) | O(V^2) |
Three details that come up in follow-ups:
- Building a heap from n items is O(n) with heapify, not O(n log n). Pushing n items one at a time is O(n log n).
- α(n) is the inverse Ackermann function, which is at most 4 or 5 for any input size you will ever see. Treat union-find operations as effectively constant.
- A balanced BST beats a hash map when you need order: floor, ceiling, range queries, or sorted iteration. In Java that is TreeMap; in C++ it is std::map. Python has no built-in balanced BST, so candidates usually reach for bisect on a sorted list (O(log n) search, O(n) insert) or a heap. Our binary tree interview questions guide covers the tree side in depth.
Hidden costs in language built-ins
Library calls look like one step but often are not. These are the ones that most often turn an O(n) solution into O(n squared). The Python wiki's TimeComplexity page documents the CPython numbers.
| Operation | Cost | Faster alternative |
|---|---|---|
Python list.pop(0) or list.insert(0, x) | O(n) | collections.deque with popleft() |
x in list | O(n) | Convert to a set once, then O(1) avg |
Slicing arr[i:j] | O(j - i) | Pass indices instead of slices |
Building a string with += in a loop | Can be O(n^2) | Collect parts in a list, then "".join() |
JavaScript array.shift() | O(n) in general | Use an index pointer or a deque |
| Sorting inside a loop | O(n log n) per iteration | Sort once, or use a heap |
If you interview in Python, the Python interview questions guide covers more of these gotchas.
Sorting Algorithms Table
Sorting is the most common hidden O(n log n) in interview solutions. Know this table well enough to explain why you would pick one algorithm over another.
| Algorithm | Best | Average | Worst | Extra space | Stable |
|---|---|---|---|---|---|
| Bubble sort | O(n) with early exit | O(n^2) | O(n^2) | O(1) | Yes |
| Insertion sort | O(n) | O(n^2) | O(n^2) | O(1) | Yes |
| Selection sort | O(n^2) | O(n^2) | O(n^2) | O(1) | No |
| Merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quicksort | O(n log n) | O(n log n) | O(n^2) | O(log n) stack avg | No |
| Heapsort | O(n log n) | O(n log n) | O(n log n) | O(1) | No |
| Timsort | O(n) | O(n log n) | O(n log n) | O(n) | Yes |
| Counting sort | O(n + k) | O(n + k) | O(n + k) | O(n + k) | Yes |
| Radix sort | O(d(n + k)) | O(d(n + k)) | O(d(n + k)) | O(n + k) | Yes |
| Bucket sort | O(n + k) | O(n + k) | O(n^2) | O(n + k) | Yes, if buckets use a stable sort |
Here k is the range of key values and d is the number of digits. Built-in sorts are what you will actually call. Python's sorted and JavaScript's Array.prototype.sort in V8 use Timsort-family algorithms, Java sorts object arrays with a Timsort and primitive arrays with dual-pivot quicksort, and C++ std::sort is typically introsort with an O(n log n) worst case. For interview purposes, state your built-in sort as O(n log n) time.
Space is the part candidates get wrong. Python's list.sort() sorts in place but Timsort can still use up to O(n) auxiliary memory, so calling it "O(1) space" is not accurate.
Algorithm Complexity Reference
Beyond sorting, these are the algorithms whose complexity interviewers expect you to know without thinking.
| Algorithm | Time | Space |
|---|---|---|
| Binary search | O(log n) | O(1) iterative |
| Two pointers / sliding window | O(n) | O(1) to O(k) |
| BFS / DFS | O(V + E) | O(V) |
| Topological sort (Kahn's) | O(V + E) | O(V) |
| Dijkstra with binary heap | O((V + E) log V) | O(V) |
| Bellman-Ford | O(V * E) | O(V) |
| Floyd-Warshall | O(V^3) | O(V^2) |
| Kruskal's MST | O(E log E) | O(V) |
| Prim's MST with heap | O(E log V) | O(V) |
| Quickselect (kth element) | O(n) avg, O(n^2) worst | O(1) iterative |
| Top k with a heap of size k | O(n log k) | O(k) |
| Subsets (backtracking) | O(n * 2^n) | O(n) recursion depth |
| Permutations (backtracking) | O(n * n!) | O(n) recursion depth |
For more on the graph entries, see the graph algorithms coding interview guide.
How to Derive Complexity in 30 Seconds
Deriving complexity is a counting exercise: how many times is each piece of work done, and what does each piece cost? This five-step method works for nearly every interview solution.
- Name your variables. n for the main input, m for a second input, V and E for graphs, k for a parameter. Never collapse two inputs into one n.
- Find the loops and recursion. Mark each loop and each recursive call.
- Count total iterations, not nesting depth. Ask how many times the innermost line runs across the whole program.
- Price every library call. Sorting, slicing,
inon a list, string concatenation, heap operations. - Add sequential parts, multiply nested parts, keep the dominant term. Then do the same for memory, including the call stack.
Worked example 1: the triangle loop
for i in range(n):
for j in range(i + 1, n):
check(arr[i], arr[j])
The inner line runs (n - 1) + (n - 2) + ... + 1 = n(n - 1)/2 times. Drop the constant and lower term: O(n squared) time, O(1) space.
Worked example 2: the doubling loop
i = 1
while i < n:
work()
i *= 2
i takes the values 1, 2, 4, 8, and so on, reaching n after about log2(n) steps. Any loop where the variable multiplies or divides by a constant is O(log n).
Worked example 3: the nested loop that is actually linear
def longest_unique(s):
seen = set()
left = best = 0
for right in range(len(s)):
while s[right] in seen:
seen.remove(s[left])
left += 1
seen.add(s[right])
best = max(best, right - left + 1)
return best
It looks quadratic because of the while inside the for. But left only moves forward and can advance at most n times in total. Each character is added once and removed at most once, so the total work is O(n). Space is O(min(n, alphabet size)) for the set. This argument, called aggregate analysis, is the one interviewers want to hear.
Worked example 4: sort then scan
def has_pair(nums, target):
nums.sort()
lo, hi = 0, len(nums) - 1
while lo < hi:
total = nums[lo] + nums[hi]
if total == target:
return True
if total < target:
lo += 1
else:
hi -= 1
return False
Sequential parts add: O(n log n) for the sort plus O(n) for the scan. The dominant term wins, so the answer is O(n log n) time. Space is O(n) auxiliary for Timsort in Python, or O(1) if you are allowed to assume an in-place heapsort.
Worked example 5: two independent inputs
def common(a, b):
lookup = set(a)
return [x for x in b if x in lookup]
Building the set is O(n), scanning is O(m) with average O(1) lookups. The answer is O(n + m) time and O(n) space. Writing O(n) here loses information and an interviewer may push back.
How to Analyze Recursion
Recursion complexity equals the number of calls multiplied by the work per call, excluding the recursive calls themselves. Drawing the recursion tree for three levels is usually enough to see the pattern.
| Recurrence | Example | Result |
|---|---|---|
| T(n) = T(n/2) + O(1) | Binary search | O(log n) |
| T(n) = T(n - 1) + O(1) | Linked list recursion, factorial | O(n) |
| T(n) = 2T(n/2) + O(1) | Tree traversal | O(n) |
| T(n) = 2T(n/2) + O(n) | Merge sort | O(n log n) |
| T(n) = T(n - 1) + O(n) | Naive selection sort, recursive | O(n^2) |
| T(n) = 2T(n - 1) + O(1) | Naive Fibonacci, all subsets | O(2^n) |
| T(n) = nT(n - 1) | Permutations | O(n!) |
The general tool for divide-and-conquer recurrences is the Master Theorem, but in interviews you almost never need its full form. Merge sort is the case to remember: log n levels, O(n) work per level, O(n log n) total.
Memoization changes the count
from functools import lru_cache
def unique_paths(m, n):
@lru_cache(maxsize=None)
def go(r, c):
if r == m - 1 or c == n - 1:
return 1
return go(r + 1, c) + go(r, c + 1)
return go(0, 0)
Without the cache, this branches twice per call and is exponential. With the cache, each (r, c) pair is computed once, so the cost is the number of states times the work per state: O(m * n) time and O(m * n) space for the cache, plus O(m + n) recursion depth. That "states times transitions" formula is how you analyze any DP; the dynamic programming patterns guide applies it to the standard problem families, and the recursion and backtracking guide covers the exponential cases.
Deriving complexity on the spot is where many candidates freeze, especially on a recursive solution they just wrote. TechScreen is an AI interview assistant built for live coding rounds that can help you check the time and space analysis of the solution you just wrote. Try it with 3 free tokens before your next practice session.
What Is Amortized Complexity?
Amortized complexity is the average cost per operation over a worst-case sequence of operations. It lets you say an operation is cheap overall even if it is occasionally expensive, with no probability involved.
The standard example is appending to a dynamic array. When the array is full, it allocates a larger block (growth factors are implementation-specific, typically between 1.125x and 2x) and copies every element, which costs O(n). Because capacity grows geometrically, those copies happen rarely enough that n appends cost O(n) in total, so each append is amortized O(1).
Other amortized results to know:
- Monotonic stack: each element is pushed once and popped at most once, so n operations cost O(n) total even though one pop loop can run long.
- Queue built from two stacks: amortized O(1) per operation, because each element moves between stacks at most once.
- Union-find with path compression and union by rank: amortized O(α(n)) per operation.
- Hash table resizing: the occasional rehash is O(n), but insertion stays O(1) amortized on average.
When an interviewer asks "isn't that pop loop O(n) each time?", the answer is to count total pushes and pops across the run, not per iteration.
How to Calculate Space Complexity
Space complexity measures the extra memory an algorithm uses as a function of input size. Most interviewers mean auxiliary space, which excludes the input itself and usually excludes the output you are required to return. Clarify which one they mean if the answer changes.
Count four sources of memory:
- Data structures you allocate: sets, maps, arrays, heaps. A hash map with one entry per element is O(n).
- The call stack: recursion depth times frame size. Recursive DFS on a skewed tree is O(n) space.
- Copies you make implicitly: slices,
sorted()returning a new list, string concatenation. - Memoization caches: one entry per state.
A useful trade-off framing: hash maps often buy time with space (Two Sum goes from O(n squared) time and O(1) space to O(n) time and O(n) space). Saying that trade-off out loud shows judgment. The guide to thinking out loud in coding interviews has phrasing for exactly these moments.
Complexity Mistakes That Cost Interview Points
These are the errors that show up most when candidates state complexity. Each one is easy to fix once you know to look for it.
| Mistake | Why it is wrong | What to say instead |
|---|---|---|
| "Hash map lookup is O(1)" with no qualifier | Worst case can be O(n) with collisions | "O(1) on average" |
| Calling sliding window O(n^2) because of the nested while | Pointers only move forward | "Each element enters and leaves once, so O(n)" |
| Ignoring the sort | A sort before a linear scan makes the whole thing O(n log n) | Add every step, keep the dominant one |
| Using n for two inputs | Hides the real cost when inputs differ in size | O(n + m) or O(n * m) |
| Forgetting recursion stack space | Every active call holds a frame | "O(h) stack space, O(n) worst case for a skewed tree" |
| Missing string costs | Strings are immutable in Python, Java, and JS | Count the length L of strings you hash, compare, or slice |
| Saying O(V^2) for BFS on an adjacency list | Each vertex and edge is processed once | O(V + E) |
| Claiming heapify on n items is O(n log n) | Bottom-up heapify is O(n) | O(n) build, O(log n) per pop |
The string point deserves extra attention. Hashing a string of length L costs O(L), so a hash map keyed by words of length L is O(L) per operation, not O(1). In problems like Group Anagrams, the honest complexity is O(n * L log L) when you sort each word as a key.
Finally, state complexity before the interviewer asks. Complexity analysis is typically graded as part of problem solving, not treated as an extra, as our breakdown of what interviewers look for in coding interviews explains. Offering it unprompted signals that you think about scale by default.
A Printable Big O Cheat Sheet Summary
If you print one block from this page, make it this one.
- O(1): array index, hash map get/set (avg), stack push/pop, deque ends, heap peek
- O(log n): binary search, balanced BST ops, heap push/pop, doubling or halving loops
- O(n): one pass, hash map counting, two pointers, sliding window, BFS/DFS (as O(V + E)), heapify, quickselect (avg)
- O(n log n): sorting, n heap operations, merge sort, Dijkstra (as O((V + E) log V))
- O(n^2): all pairs, naive nested loops, DP over two indices of the same array
- O(2^n): subsets, naive recursion with two branches
- O(n!): permutations
You can know every row of this cheat sheet and still blank when the interviewer asks "can you do better than O(n log n)?" TechScreen is an AI interview assistant that can suggest faster approaches, edge cases, and complexity checks while you work. Try it with 3 free tokens before your next coding round.
Frequently Asked Questions
What is Big O notation in simple terms?
Big O notation describes how the running time or memory use of an algorithm grows as the input size grows. It keeps only the fastest-growing term and drops constants, so 3n + 10 becomes O(n). In interviews it is a shorthand for scalability: an O(n) solution handles a million items easily, while an O(n squared) solution on the same input may need around a trillion operations and time out.
What are the most common Big O complexities in order?
From fastest to slowest, the classes you will see most often are O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n squared) quadratic, O(n cubed) cubic, O(2 to the n) exponential, and O(n!) factorial. Most accepted interview solutions land between O(n) and O(n log n). Exponential and factorial solutions are usually only acceptable when the input is tiny, such as n of 20 or less.
Is a hash map lookup really O(1)?
A hash map lookup is O(1) on average, not in the worst case. With a good hash function and resizing, keys spread evenly and each lookup checks a constant number of slots. If many keys collide, a lookup can degrade to O(n) in simple implementations. Java's HashMap converts large collision buckets into balanced trees, which caps that worst case at O(log n). In interviews, saying average O(1) is the accurate answer.
How do you find the time complexity of a recursive function?
Count two things: how many calls the recursion makes in total and how much work each call does outside its recursive calls. Multiply them. A function that calls itself twice on n minus 1 builds a tree with about 2 to the n calls. A function that halves the input and does linear work per level, like merge sort, has log n levels of O(n) work, which gives O(n log n). Memoization caps total calls at the number of distinct states.
Does recursion count toward space complexity?
Yes. Every active recursive call holds a stack frame, so recursion uses space proportional to its maximum depth, even if you allocate no other data. A DFS on a tree with height h uses O(h) stack space, which is O(log n) for a balanced tree and O(n) for a skewed one. Interviewers often ask about this, so mention the call stack explicitly when you state space complexity.
What is the difference between amortized and average time complexity?
Amortized complexity is a guarantee about a sequence of operations: the total cost of n operations divided by n, even in the worst case. Dynamic array append is amortized O(1) because rare O(n) resizes are paid for by many cheap appends. Average complexity is about expected cost over random inputs or random choices, such as hash table lookups or quicksort with a random pivot. Amortized bounds involve no probability at all.
Do interviewers care about Big O constants?
Rarely in the stated answer, sometimes in the discussion. You should report O(n), not O(2n), because Big O drops constants. But when two solutions share the same Big O, an interviewer may ask which is faster in practice, and then constants, memory locality, and hidden library costs matter. A single pass is often preferred over three passes even though both are O(n).
Ready to use AI assistance in your next interview?
TechScreen is the invisible AI assistant trusted by engineers interviewing at Google, Meta, Amazon, and hundreds of other companies. Start with 3 free tokens — no credit card required.
Ace your next interview →