What “complexity” means in practice
When you compare two approaches, you are usually comparing how their work and resource use grow as the input size grows. You do not need heavy math to build good intuition; you need a habit of counting at a high level:
- Work (time): roughly how many basic steps happen (comparisons, additions, assignments, lookups, swaps, reads/writes).
- Resources (space): how much extra memory is needed beyond the input (extra arrays, hash tables, recursion stack, buffers).
We will use n for “number of items” (records, numbers, characters). The goal is not exact instruction counts, but a reliable estimate: “about one pass over the data” vs “about one pass per item” vs “about repeated halving.”
Counting operations without deep math
Step 1: Choose a unit of work
Pick a simple unit that matches the task. Examples:
- Searching: “one comparison with an item”
- Summing: “one addition per item”
- Sorting: “one comparison between two items” (plus swaps/moves)
- String processing: “one character check”
Be consistent when comparing two approaches to the same task.
Step 2: Count per item, per pass
Many algorithms are easiest to estimate by answering:
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
- How many passes over the data?
- How many steps per item per pass?
Example: “two passes, each doing ~3 operations per item” is about 6n operations.
Step 3: Identify growth patterns
Common growth patterns you can recognize by structure:
| Pattern | Intuition | Typical structure |
|---|---|---|
| Constant | Doesn’t grow with n | Fixed number of steps |
| Linear | One pass over items | Single loop over n |
| Quadratic | Work per item grows with n | Nested loops over n |
| Logarithmic | Repeatedly halves the problem | Binary search, divide-and-conquer steps |
| Linearithmic | Linear work across log-sized levels | Many efficient sorts, merge-like processes |
You do not need to memorize names; focus on the story: “one pass,” “one pass per item,” “keep halving.”
Comparing growth with small input-size examples
A practical way to compare approaches is to plug in a few input sizes and estimate step counts. Suppose:
- Approach A does about
5nsteps (linear). - Approach B does about
n^2steps (quadratic).
| n | A: 5n | B: n² | Which is smaller? |
|---|---|---|---|
| 10 | 50 | 100 | A |
| 100 | 500 | 10,000 | A |
| 1,000 | 5,000 | 1,000,000 | A by far |
Even if constants differ (maybe B has a smaller constant factor), the growth pattern often dominates as n becomes large.
Side-by-side comparison 1: Finding a number in a list
Task
Given a list of n numbers and a target value, decide whether the target exists.
Approach A: Linear scan (unsorted list)
for each item in list: // n items (one pass) if item == target: return foundreturn not foundHigh-level count:
- Worst case: check all
nitems → aboutncomparisons. - Best case: first item matches → about 1 comparison.
Space: constant extra memory.
Approach B: Binary search (sorted list)
low = 0, high = n-1while low <= high: // each loop halves range mid = (low+high)//2 if list[mid] == target: return found else if list[mid] < target: low = mid+1 else: high = mid-1return not foundHigh-level count:
- Each iteration cuts the remaining search space roughly in half.
- Number of iterations is “how many times can you halve
nuntil you get to 1?” (logarithmic growth). - For intuition:
n=1,024halves to 1 in about 10 steps.
Space: constant extra memory.
Trade-offs
- If the list is already sorted (or you will do many searches), binary search scales much better.
- If you only do one search and the list is unsorted, sorting just to search may cost more overall than a single linear scan.
Side-by-side comparison 2: Detecting duplicates
Task
Given a list of n items, determine whether any value appears more than once.
Approach A: Compare every pair (no extra memory)
for i from 0 to n-1: for j from i+1 to n-1: if list[i] == list[j]: return duplicate foundreturn no duplicatesHigh-level count:
- For each item, compare it to many later items.
- Roughly
(n-1) + (n-2) + ... + 1comparisons, which grows liken².
Space: constant extra memory.
Approach B: Track seen items with a set (extra memory)
seen = empty setfor each item in list: // one pass if item in seen: return duplicate found add item to seenreturn no duplicatesHigh-level count:
- One pass over
nitems. - Each membership check and insert is typically “about constant time” on average.
- Total work grows roughly like
n.
Space: extra memory for up to n items in the set.
Trade-offs
- If memory is tight, the pairwise approach uses almost no extra space but can become very slow as
ngrows. - If speed matters and memory is available, the set-based approach usually scales far better.
Side-by-side comparison 3: Counting frequencies
Task
Given n words, produce a frequency table (word → count).
Approach A: For each word, scan a growing list of counters
counts = empty list of (word, count)for each word w in input: found = false for each entry e in counts: // may grow up to n if e.word == w: e.count += 1 found = true break if not found: append (w, 1)High-level count:
- Outer loop runs
ntimes. - Inner scan can be small at first but can grow toward
n. - Worst case (all words distinct): about
1 + 2 + ... + (n-1)comparisons → grows liken².
Space: stores up to n counters.
Approach B: Use a map/dictionary
counts = empty mapfor each word w in input: // one pass if w not in counts: counts[w] = 0 counts[w] += 1High-level count:
- One pass over
n. - Each lookup/update is typically about constant time on average.
- Total work grows roughly like
n.
Space: stores up to n keys and counts.
When “faster growth” can still be acceptable
Sometimes an approach with worse growth is still fine because constraints are small or because it is simpler to implement and less error-prone.
Use constraints to decide
Suppose you have two options:
- Option 1:
n²comparisons, minimal memory. - Option 2:
10noperations, but needs extra memory proportional ton.
If n is at most 200, then n² = 40,000 comparisons might be perfectly acceptable. If n can be 2,000,000, then n² is impossible, and you must choose a near-linear approach (or change the problem setup).
Constant factors and hidden costs
High-level counting ignores some real costs:
- Memory allocation and cache behavior can make “extra space” slower or faster depending on access patterns.
- Hash-based structures are usually fast on average, but have overhead per operation.
- Sorting has a cost, but can enable many fast queries afterward.
Still, growth patterns are the first filter: they tell you what will break as n scales.
Practical step-by-step: Estimating work for an approach
Example: “Two passes plus a nested loop”
Imagine an algorithm that:
- Pass 1: scans all items once doing ~2 operations per item → about
2n - Pass 2: scans all items once doing ~3 operations per item → about
3n - Then: for each item, compares it to every item (nested loop) → about
n²
Total estimate: 2n + 3n + n². For large n, the n² term dominates, so the overall growth behaves like quadratic.
Example: “Preprocess then answer many queries”
You have n items and q queries.
- Approach A: For each query, scan the list → about
q * ncomparisons. - Approach B: Build an index (e.g., set/map) once → about
ninserts, then each query is ~constant → aboutn + q.
If q is large, preprocessing is usually worth it. If q is tiny, scanning may be simpler and good enough.
Exercises: Choose the better-scaling approach
For each exercise: (1) estimate step counts using “per item” and “per pass,” (2) decide which scales better as input grows, (3) mention any resource constraints (memory, preprocessing time, number of queries).
1) Membership queries: scan vs preprocess
You have a list of n product IDs and you must answer q questions: “Is ID X in the list?”
- Approach A: For each query, scan the list from start to end.
- Approach B: Build a set of all IDs once, then answer each query by checking the set.
Estimate work for n=100,000 and (a) q=10, (b) q=1,000,000. Which approach is better in each case, and why?
2) Duplicate detection under memory limits
You must detect duplicates in a list of n integers. You are told extra memory must be under 1 MB.
- Approach A: Use a set to track seen values.
- Approach B: Compare every pair (nested loops).
Assume n might be 50,000. Which approach is feasible under the memory constraint? If neither is feasible, explain what the constraint suggests about needing a different strategy (e.g., external storage, sorting in-place, or chunking).
3) Sorting to enable faster repeated operations
You need to answer q queries: “How many numbers are ≤ X?” on a list of n numbers.
- Approach A: For each query, scan all
nnumbers and count. - Approach B: Sort once, then for each query use a binary-search-like method to find the boundary.
Use step estimates to compare q*n versus “sort cost + q times halving.” For n=1,000,000, compare q=5 and q=50,000.
4) Frequency counting: list of counters vs map
You process n words and want counts per word.
- Approach A: Keep a list of (word,count) pairs and linearly search it for each word.
- Approach B: Use a map/dictionary keyed by word.
Consider two cases: (a) almost all words are distinct, (b) there are only 100 distinct words repeated many times. Estimate the work in each case and explain how the number of distinct keys affects Approach A.
5) Nested loops with early exit
An algorithm checks whether any pair of items violates a rule. It uses nested loops but stops immediately when it finds a violation.
- Case A: Violations are common and usually found quickly.
- Case B: Violations are rare and usually not present.
Explain how the same code can behave very differently depending on input, and give a high-level estimate of best-case vs worst-case work.