Complexity Analysis
Practice: Analyzing Complexity of Code Snippets
for item in items: # Loop A: O(n)
Jr Codex DSA Notes
Level: Beginner Prerequisites: Chapter 5 Time to complete: ~25 minutes
Table of Contents
- A Checklist for Analyzing Any Function
- Worked Example 1: Two Independent Loops
- Worked Example 2: A Loop with a Halving Step
- Worked Example 3: A Hidden Nested Loop
- Worked Example 4: Recursion
- Common Traps to Watch For
- Try It Yourself
- Summary & Next Steps
1. A Checklist for Analyzing Any Function
- Identify the input(s) and what
nrepresents (list length? string length? tree nodes?). - Find every loop — is it a single pass, or nested inside another loop?
- Check whether any loop's range shrinks (like halving) rather than staying proportional to
n. - Check for function calls inside loops — does the called function have its own non-constant complexity?
- Check for recursion — how many calls happen, and how deep does the stack go?
- Combine using the rules from Chapter 3: sequential adds, nested multiplies, then keep only the dominant term.
- Separately consider auxiliary space using Chapter 4's rules (new structures, recursion stack).
2. Worked Example 1: Two Independent Loops
def summary_stats(items):
total = 0
for item in items: # Loop A: O(n)
total += item
maximum = items[0]
for item in items: # Loop B: O(n), NOT nested inside Loop A
if item > maximum:
maximum = item
return total, maximumAnalysis: Loop A and Loop B are sequential, not nested → O(n) + O(n) = O(2n) → simplifies to O(n). Space: only a couple of scalar variables → O(1) auxiliary space.
3. Worked Example 2: A Loop with a Halving Step
def count_halvings(n):
steps = 0
while n > 1:
n = n // 2 # the range shrinks by half each iteration
steps += 1
return stepsAnalysis: Each iteration cuts n in half rather than stepping through it linearly — this is the defining signature of O(log n) from Chapter 2. Space: one counter variable → O(1).
4. Worked Example 3: A Hidden Nested Loop
def has_common_element(list_a, list_b):
for a in list_a: # O(n)
if a in list_b: # `in` on a list is itself O(m) — HIDDEN LOOP
return True
return FalseAnalysis: It looks like a single loop, but in on a list performs its own linear scan. With list_a of size n and list_b of size m, this is O(n × m) — effectively quadratic if the lists are similar in size. This is exactly the "function call inside a loop" trap from Chapter 3 — converting list_b to a set first (Module 8) would drop this to O(n + m).
5. Worked Example 4: Recursion
def sum_list(items):
if not items:
return 0
return items[0] + sum_list(items[1:]) # note: items[1:] COPIES the listAnalysis: This makes n recursive calls → O(n) time, seemingly. But items[1:] creates a new list copy at every call, and that copy itself takes O(k) time/space where k is the remaining length. Summed across all calls, that's O(n) + O(n-1) + ... + O(1) = O(n²) time, and the recursion stack itself adds O(n) space on top of the copies. This is a classic hidden-cost trap — the fix is to pass an index instead of slicing:
def sum_list_fixed(items, index=0):
if index == len(items):
return 0
return items[index] + sum_list_fixed(items, index + 1)
# No copying — O(n) time, O(n) space (call stack only)6. Common Traps to Watch For
- Slicing inside recursion or loops (
items[1:],items[:k]) silently costsO(k)per slice — it's not free. inon a list isO(n), notO(1)— onlyinon asetordictisO(1)average case (Module 8).- String concatenation in a loop (
result += char) can beO(n²)overall in some languages/implementations, since each+=may create a new string — prefer building a list and''.join()at the end. - Sorting inside a loop — calling
.sort()(O(n log n)) inside anO(n)loop makes the whole functionO(n² log n), easy to miss at a glance.
7. Try It Yourself
Before continuing to Module 2, determine the time and space complexity of each:
# (a)
def mystery_a(n):
for i in range(n):
for j in range(n):
for k in range(n):
print(i, j, k)
# (b)
def mystery_b(items):
return items[0] + items[-1]
# (c)
def mystery_c(items, target):
seen = set()
for item in items:
if target - item in seen:
return True
seen.add(item)
return FalseAnswers (click to expand)
- (a) Three nested loops, each
O(n)→O(n³)time,O(1)space. - (b) Two constant-time index lookups →
O(1)time,O(1)space. - (c) A single pass building a set, with
O(1)average-case set lookups →O(n)time,O(n)space (this is the classic "Two Sum" pattern you'll meet formally in Module 8).
8. Summary & Next Steps
Key Takeaways
- Analyzing complexity is mechanical: identify loops/recursion, check for hidden costs (slicing,
inon lists, sorting), then apply the add/multiply rules. - Slicing, list-membership checks, and sorting are the most common sources of hidden complexity that make code look better than it actually performs.
- This checklist will be applied to every algorithm from here forward — it's worth returning to if a later module's complexity claim (e.g., "this is
O(n)") isn't obvious to you.
Concept Check
- Why does
items[1:]inside a recursive call change the overall time complexity fromO(n)toO(n²)? - Why is
target - item in seenin Example (c)O(1)on average, buttarget - item in itemswould not be? - Walk through Example (a) and confirm why it's
O(n³), notO(n²).
Next Module
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index