How to Analyze Algorithms

Hereโ€™s a problem you hit the moment you write any loop. You look at your code and someone asks, โ€œHow fast is this?โ€ And you have no idea what to say. You feel it runs fine on your laptop, but you canโ€™t prove it will stay fast when the input gets big.

That is exactly what analyzing an algorithm fixes. Analyzing an algorithm means looking at the code and working out how the running time grows as the input grows. We measure that growth with Big O. The good news is there is a simple method. You can use it every time. So let me walk you through it step by step. Then weโ€™ll analyze real code in five languages.

๐ŸŽฏ What We Are Really Counting

Before any rules, letโ€™s be clear about what we measure. We do not measure seconds on a clock. Different computers run at different speeds, so seconds tell us nothing useful.

Instead we count basic operations. A basic operation is one simple step the computer does, like one comparison, one addition, or one array access. We ask one question: as the input size n grows, how does the number of basic operations grow? That growth is the whole story.

n is the size of the input

Throughout this tutorial, n means the size of the input. If you pass an array of 100 items, then n is 100. Big O describes how the work grows as n gets larger and larger.

๐Ÿ› ๏ธ The Method to Find Big O

Here is the recipe you will use every single time. The key idea is simple. You count the basic operations, then nested loops multiply, then you drop the small stuff. Follow these steps in order.

  1. Count the basic operations. Look at the code and count how many simple steps run for an input of size n.
  2. A single loop over the input is O(n). If a loop runs once for each of the n items, that is linear time.
  3. Nested loops multiply. A loop inside a loop, each running n times, gives n ร— n, which is O(n^2).
  4. Drop the constants. A step that always runs a fixed number of times, no matter how big n is, counts as O(1). And 2n is just O(n); we throw away the 2.
  5. Keep the biggest term. If you get something like n^2 + n, only the largest part matters when n is huge. So keep n^2 and drop the rest.

That is the entire method. Now letโ€™s see why each step makes sense and run it on real code.

1๏ธโƒฃ A Single Loop Is O(n)

Letโ€™s start with the simplest case. We have an array, and we want to add up all its numbers. We touch each item exactly once.

So why is this O(n)? Because the loop body runs once for each item:

  • If the array has 10 items, the loop runs 10 times.
  • If it has 1,000 items, it runs 1,000 times.
  • The work grows in a straight line with n. That is linear time, written O(n).

The steps outside the loop, like creating the total variable and returning it, run just once. They do not depend on n, so they are O(1) and we ignore them next to the loop. Now letโ€™s code it.

Sum an array with a single loop in Python

sum_array.py
# Adds up every element. The loop runs once per item, so this is O(n).
def sum_array(arr):
total = 0 # runs once -> O(1)
for x in arr: # runs n times -> O(n)
total += x # one basic operation each pass
return total # runs once -> O(1)
arr = [10, 20, 30, 40, 50]
print("Sum =", sum_array(arr))

The output of the above code will be:

Sum = 150

Complexity of a single loop

Time Complexity: O(n) โ€” the loop body runs once per item, so the work grows in a straight line with the input size. Space Complexity: O(1) โ€” we only keep one total variable, no matter how big the array gets.

2๏ธโƒฃ Nested Loops Multiply Into O(n^2)

Now letโ€™s put a loop inside a loop. Say we want to print every pair of items in the array. For each item we walk the whole array again.

So why O(n^2) here? Because the inner loop runs fully for each turn of the outer loop:

  • The outer loop runs n times.
  • For each of those, the inner loop also runs n times.
  • So the body runs n ร— n, which is n^2 times.

When loops are nested like this, you multiply their counts. If the array has 5 items, the inner body runs 25 times. Bump it to 1,000 items and it jumps to a million. That fast growth is what O(n^2) warns you about. Letโ€™s code it.

all_pairs.py
# A loop inside a loop. Each runs n times, so the body runs n*n -> O(n^2).
def print_pairs(arr):
for i in arr: # outer loop runs n times
for j in arr: # inner loop runs n times each
print(f"({i},{j})", end=" ")
print()
arr = [1, 2, 3]
print_pairs(arr)

The output of the above code will be:

(1,1) (1,2) (1,3) (2,1) (2,2) (2,3) (3,1) (3,2) (3,3)

Two loops side by side do NOT multiply

Multiplying only happens when one loop sits inside another. Two separate loops one after the other are added, not multiplied. So n + n is 2n, which is still O(n). Only nesting gives you O(n^2).

Complexity of nested loops

Time Complexity: O(n^2) โ€” the inner loop runs n times for each of the n outer turns, so the work grows with the square of the input. Space Complexity: O(1) โ€” we are not storing anything that grows with n.

3๏ธโƒฃ Drop Constants and Keep the Biggest Term

This is the step that confuses people, so letโ€™s slow down. Imagine a function that does a few separate things on the same array. It runs a couple of loops, then one nested loop.

Counting the raw operations, we might get something like 2n + n^2. That looks messy. Big O cleans it up with two rules.

First, drop the constants. We do not care about the 2 in 2n. Constants do not change how the work grows. Even doubling the computer speed only changes the constant. We are measuring growth, not exact counts. So 2n becomes n.

Then keep the biggest term. We had n + n^2. When n is small the two look close, but watch what happens as n grows:

  • At n = 10, you get 10 + 100 = 110. The n^2 part is already most of it.
  • At n = 1000, you get 1000 + 1000000. The n part is now a tiny crumb.
  • So as n heads toward infinity, the n^2 term swallows everything else.

That is why we keep only the largest term and drop the rest. The answer is O(n^2). Letโ€™s see it in code.

Mixed work, then reduce to one term in Python

mixed_work.py
# Two single loops (n + n = 2n) plus one nested loop (n^2).
# Total 2n + n^2 reduces to O(n^2) after dropping constants and smaller terms.
def analyze(arr):
n = len(arr)
single = 0
pairs = 0
for x in arr: # O(n)
single += x
for x in arr: # O(n)
single += x * 2
for i in range(n): # O(n^2)
for j in range(n):
pairs += 1
print("single work counted twice =", single)
print("nested pairs counted =", pairs)
analyze([1, 2, 3, 4])

The output of the above code will be:

single work counted twice = 30
nested pairs counted = 16

The shortcut you'll use forever

Once you trust these rules, you stop counting every line. You just scan for the worst part of the code. One nested loop over the input? Itโ€™s O(n^2). One plain loop? O(n). No loop that depends on n? O(1). The biggest piece wins.

๐Ÿ“Š Quick Reference for Common Shapes

Most code you analyze falls into one of a few shapes. Here is a cheat sheet you can match your code against.

Code shape Big O Why
No loop over the input O(1) A fixed number of steps, no matter how big n is
One loop over n items O(n) Work grows in a straight line with n
Two separate loops O(n) n + n = 2n, and we drop the constant
A loop inside a loop O(n^2) Nested loops multiply: n times n
Halving the input each step O(log n) The input shrinks fast, like in binary search

โš ๏ธ Common Mistakes and Misconceptions

A few traps catch beginners over and over. Letโ€™s clear them out.

  • Keeping the constants. Writing O(2n) or O(n + 5) misses the point. Big O ignores constants, so it is just O(n). The notation is about growth, not exact counts.
  • Adding nested loops instead of multiplying. A loop inside a loop is n ร— n, not n + n. The multiply is what makes it O(n^2).
  • Multiplying loops that are not nested. Two loops one after the other are added. They stay O(n). Only nesting multiplies.
  • Keeping every term. Once you have n^2 + n + 5, only n^2 survives. The smaller pieces vanish as n grows, so drop them.
  • Counting a fixed-size loop as O(n). A loop that always runs 100 times, no matter the input, is O(1). It only counts as O(n) if it grows with n.

๐Ÿงฉ What Youโ€™ve Learned

You can now look at a piece of code and name its Big O with confidence. Hereโ€™s what youโ€™ve picked up.

  • โœ… We measure growth in basic operations as n grows, never in seconds on a clock.
  • โœ… A single loop over the input is O(n), and a fixed number of steps is O(1).
  • โœ… Nested loops multiply, so a loop inside a loop over n items is O(n^2).
  • โœ… Drop the constants, because 2n is just O(n).
  • โœ… Keep only the biggest term, so n^2 + n reduces to O(n^2).
  • โœ… The shortcut is to scan for the worst part of the code, since the largest term wins.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is the time complexity of a single loop that runs once for each of the n items?

    Why: A single loop over n items runs n times, so the work grows in a straight line with the input, which is O(n).

  2. 2

    You count 2n + n^2 operations. What is the Big O?

    Why: Drop the constant on 2n and keep only the largest term, n^2, so the answer is O(n^2).

  3. 3

    A loop over n items sits inside another loop over n items. What is the complexity?

    Why: Nested loops multiply, so n times n gives n^2, which is O(n^2).

  4. 4

    Two separate loops, each over n items, run one after the other. What is the Big O?

    Why: Separate loops are added, giving n + n = 2n, and after dropping the constant it is O(n).

๐Ÿš€ Whatโ€™s Next?

Youโ€™ve got the method. Now connect it to the ideas it builds on and the resources it touches.

  • Big O Notation explains the notation itself and the full family of growth rates.
  • Time Complexity goes deeper on how running time grows for different algorithms.
  • Space Complexity does the same analysis for the memory your code uses.

Try this method on the next loop you write, and โ€œhow fast is this?โ€ will turn into a quick, confident answer.

Share & Connect