DSA course menu
How to Analyse Time Complexity of Code (Step by Step)
Work out the Big O time complexity of real code: single loops, nested loops, halving loops, sequential steps, function calls and Python built-ins, with a table of common operation costs.
On this page
To find the time complexity of code, ask one question: how many times does the most repeated line run, as a function of n? Then simplify with the Big O rules.
A single loop: O(n)#
def total(items):
result = 0
for x in items: # runs n times
result += x # O(1) each time
return result
print(total([4, 8, 15, 16, 23, 42]))108The loop body is constant work and it runs once per item: n × O(1) = O(n).
Sequential steps add up#
def stats(items):
smallest = min(items) # O(n)
largest = max(items) # O(n)
first = items[0] # O(1)
return smallest, largest, first
print(stats([7, 2, 9, 4]))(2, 9, 7)O(n) + O(n) + O(1) = O(2n + 1) = O(n). Steps that happen one after another are added, then simplified.
Nested loops multiply#
def has_duplicate(items):
for i in range(len(items)): # n times
for j in range(i + 1, len(items)): # up to n times
if items[i] == items[j]:
return True
return False
print(has_duplicate([3, 1, 4, 1, 5]))
print(has_duplicate([2, 7, 1, 8]))True
FalseFor each of the n outer iterations, the inner loop runs up to n times: O(n²). Loops inside loops multiply.
A set solves the same problem in O(n), because checking x in seen is O(1) on average:
def has_duplicate_fast(items):
seen = set()
for x in items: # n times
if x in seen: # O(1) on average
return True
seen.add(x)
return False
print(has_duplicate_fast([3, 1, 4, 1, 5]))TrueTrading a little memory for a lot of speed like this is one of the most common moves in DSA.
Halving loops: O(log n)#
When each step halves what is left, the loop runs about log₂ n times:
def halvings(n):
count = 0
while n > 1:
n //= 2
count += 1
return count
for n in [16, 1024, 1_000_000]:
print(n, halvings(n))16 4
1024 10
1000000 19A million only needs 19 halvings. This is why binary search is so fast.
Loops that do not depend on n: O(1)#
def first_three_sum(items):
total = 0
for i in range(3): # always 3 times
total += items[i]
return totalThe loop runs 3 times whether the list has 3 items or 3 million: O(1).
Different inputs keep different letters#
def common(a, b):
b_set = set(b) # O(m)
return [x for x in a if x in b_set] # O(n)
print(common([1, 2, 3, 4], [3, 4, 5]))[3, 4]With len(a) = n and len(b) = m, this is O(n + m). Do not merge them into a single n unless they are the same size.
The cost of Python built-ins#
Built-in operations hide loops. These are the costs to remember:
"Amortized" means occasionally one call is slow, but averaged over many calls each is O(1). You will see why in Arrays and Dynamic Arrays.
Common mistakes#
Practice#
Practice exercise
What is the time complexity of each function? n is len(items).
def f1(items):
i = 1
while i < len(items):
i *= 2
return i
def f2(items):
count = 0
for x in items:
i = len(items)
while i > 0:
i //= 2
count += 1
return count
def f3(items):
return sorted(items)[0]Show hint
f1 doubles i each time. In f2, a halving loop sits inside a loop that runs n times.
Show solution
f1:idoubles each step until it reaches n, so about log₂ n steps: O(log n).f2: n outer iterations × log n inner steps: O(n log n).f3: sorting is O(n log n); taking the first item is O(1): O(n log n). (Usingmin(items)instead would be O(n).)
Counting the steps of f2 confirms it grows like n log n:
def f2(items):
count = 0
for x in items:
i = len(items)
while i > 0:
i //= 2
count += 1
return count
for n in [8, 16, 1024]:
print(n, f2(range(n)))8 32
16 80
1024 11264Key takeaway#
Next: memory matters too. See Space Complexity.
Finished this lesson?