Python — Loops

Why the inner loop finishes first

The answer is in the indentation. In Python, indentation isn't style — it defines scope, and scope means obligation. A loop must run everything inside it to completion before it can move on.

The code
loops.py
fruits  = ["banana", "apple", "orange"]
numbers = [1, 2, 3]

for fruit in fruits:          # outer scope — pauses here until its body is done
    for number in numbers:      # inner scope — must exhaust numbers before returning
        print(fruit, number)    # only line inside the inner scope
Scope = obligation
Outer loop

When Python hits for fruit in fruits:, it picks the first fruit and then looks one level deeper — at everything indented beneath it.

It cannot advance to the next fruit until every single thing indented under it has finished running. That indented block happens to be another entire loop.

fruit = "banana"
→ must run inner loop completely
→ only then: fruit = "apple"
Inner loop

The inner loop works the same way. For each number, it must run everything indented under it — just one print() — before moving to the next number.

Only after it cycles through all of numbers does control return up to the outer loop.

number = 1 → print("banana", 1)
number = 2 → print("banana", 2)
number = 3 → print("banana", 3)
← done. outer loop may continue.
The rule: In Python, indentation is scope, and scope is a promise. A for loop promises to run its entire body — everything indented under it — for each item before it moves on. The outer loop's "body" happens to be a complete inner loop. So the outer loop is stuck waiting every time, while the inner loop does its full job.
Full execution trace
# outer scope (fruit) inner scope (number) print() output who's waiting?
1banana1banana 1outer
2banana2banana 2outer
3banana3banana 3outer — inner done, releases
4apple1apple 1outer
5apple2apple 2outer
6apple3apple 3outer — inner done, releases
7orange1orange 1outer
8orange2orange 2outer
9orange3orange 3outer done too
Complexity
Big-O notation Big-O is a way of describing how the number of steps in your code grows as the input gets larger. It ignores constants and small details — it only cares about the shape of the growth. O(n) means steps grow linearly with input size. O(n²) means if your input doubles, your work quadruples. It's a worst-case ceiling, not an exact count.
n
len(fruits)
×
m
len(numbers)
=
O(n·m)
total steps
The outer loop runs n times. For each of those, the inner loop runs m times — because of scope, the inner loop can't share time with the outer one. Total work is always n × m. When the lists are the same length that simplifies to O(n²), but the general case is O(n·m) because the lists could differ.
Why this matters for LeetCode
Time Limit Exceeded

LeetCode doesn't just check whether your answer is correct — it checks whether it's fast enough. Every problem has a hidden time limit, typically around 1–2 seconds. If your solution takes too long on large inputs, you get TLE (Time Limit Exceeded) even if the output is right.

LeetCode tests your solution against inputs that can have n up to 10⁵ or 10⁶. At that scale, the difference between O(n) and O(n²) isn't academic:

complexity n = 1,000 n = 10,000 n = 100,000 verdict
O(n) 1,000 10,000 100,000 ✓ passes
O(n log n) ~10,000 ~130,000 ~1,700,000 ✓ usually passes
O(n²) 1,000,000 100,000,000 10,000,000,000 ✗ TLE

A nested loop over the same list — like checking every pair of elements — is the classic O(n²) pattern. LeetCode problems that look like they need a nested loop usually have an O(n) or O(n log n) trick: a hash map, a two-pointer, sorting first. Recognizing that your nested loop is O(n²) is the first step to knowing you need to find that trick.