Time Complexity
Time complexity measures how the runtime of an algorithm scales with input size. It’s expressed using Big O notation.
How to Calculate Time Complexity
Step 1: Identify the Input Size
Usually n represents the main input (array length, string length, etc.).
Step 2: Count Operations
Count how many times basic operations execute relative to n.
Step 3: Keep the Dominant Term
Drop constants and lower-order terms.
Common Time Complexities
O(1) - Constant Time
Operations that don’t depend on input size.
def get_first(arr): return arr[0] # Always 1 operation
def swap(arr, i, j): arr[i], arr[j] = arr[j], arr[i] # Always 3 operations → O(1)O(log n) - Logarithmic Time
Halving the problem space each step.
def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: # Runs log₂(n) times mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1Why log n? If n = 16, we need 4 steps (16 → 8 → 4 → 2 → 1). log₂(16) = 4.
O(n) - Linear Time
Iterating through input once.
def find_sum(arr): total = 0 for num in arr: # Runs n times total += num # O(1) per iteration return total# Total: n × O(1) = O(n)O(n log n) - Linearithmic Time
Common in efficient sorting algorithms.
def merge_sort(arr): if len(arr) <= 1: return arr
mid = len(arr) // 2 left = merge_sort(arr[:mid]) # T(n/2) right = merge_sort(arr[mid:]) # T(n/2) return merge(left, right) # O(n)
# Recurrence: T(n) = 2T(n/2) + O(n) → O(n log n)O(n²) - Quadratic Time
Nested loops over the input.
def bubble_sort(arr): n = len(arr) for i in range(n): # Runs n times for j in range(n - 1): # Runs n-1 times if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j]# Total: n × (n-1) = n² - n → O(n²)O(2ⁿ) - Exponential Time
Doubling with each additional input element.
def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2)
# Each call makes 2 more calls# Total calls roughly doubles each level → O(2ⁿ)Analyzing Loops
Single Loop
for i in range(n): # O(n) print(i)Nested Loops (Independent)
for i in range(n): # O(n) for j in range(n): # O(n) print(i, j)# Total: O(n) × O(n) = O(n²)Nested Loops (Dependent)
for i in range(n): for j in range(i): # Runs 0+1+2+...+(n-1) = n(n-1)/2 print(i, j)# Total: O(n²)Loop with Halving
i = nwhile i > 0: print(i) i = i // 2 # n → n/2 → n/4 → ... → 1# Total: O(log n)Loop with Doubling
i = 1while i < n: print(i) i = i * 2 # 1 → 2 → 4 → ... → n# Total: O(log n)Analyzing Recursion
Linear Recursion
def factorial(n): if n <= 1: return 1 return n * factorial(n - 1) # n calls total → O(n)Binary Recursion
def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2) # O(2ⁿ)Divide and Conquer
Use the Master Theorem for T(n) = aT(n/b) + O(n^d):
- If d < log_b(a): O(n^log_b(a))
- If d = log_b(a): O(n^d log n)
- If d > log_b(a): O(n^d)
Merge Sort: T(n) = 2T(n/2) + O(n)
- a=2, b=2, d=1
- log₂(2) = 1 = d
- Therefore: O(n log n)
Multiple Inputs
When you have multiple inputs, use different variables:
def print_pairs(arr1, arr2): # arr1 has n elements, arr2 has m for a in arr1: # O(n) for b in arr2: # O(m) print(a, b)# Total: O(n × m), NOT O(n²)Sequential vs Nested
Sequential Operations (Add)
def process(arr): for x in arr: # O(n) print(x) for x in arr: # O(n) print(x * 2)# Total: O(n) + O(n) = O(2n) = O(n)Nested Operations (Multiply)
def process(arr): for x in arr: # O(n) for y in arr: # O(n) print(x, y)# Total: O(n) × O(n) = O(n²)Best, Average, Worst Case
| Algorithm | Best | Average | Worst |
|---|---|---|---|
| Linear Search | O(1) | O(n) | O(n) |
| Binary Search | O(1) | O(log n) | O(log n) |
| Quick Sort | O(n log n) | O(n log n) | O(n²) |
| Insertion Sort | O(n) | O(n²) | O(n²) |
Big O typically represents the worst case unless specified otherwise.
Practice: Analyze These
Code 1
def mystery(n): count = 0 for i in range(n): for j in range(n): for k in range(n): count += 1 return countAnswer: O(n³) - Three nested loops, each running n times.
Code 2
def mystery(n): count = 0 i = 1 while i < n: j = 1 while j < n: count += 1 j *= 2 i *= 2 return countAnswer: O(log²n) - Both loops run log(n) times.
Code 3
def mystery(arr): n = len(arr) for i in range(n): for j in range(i + 1, n): print(arr[i], arr[j])Answer: O(n²) - Inner loop runs n-1, n-2, …, 1 times = n(n-1)/2.
Key Takeaways
- Count operations relative to input size
- Drop constants and lower-order terms
- Nested loops multiply, sequential loops add
- log n appears when halving/doubling
- Use Master Theorem for divide-and-conquer recursion