A loop is a control flow construct that causes a sequence of statements to execute repeatedly, either for a fixed number of iterations (for loop), while a condition holds true (while loop), or over each element of a collection (for-each / range-based loop), with the ability to exit early (break) or skip to the next iteration (continue).
Repeating actions — the engine of programs
What you'll learn: for loops (repeat for each item in a collection), while loops (repeat until a condition is false), break/continue, and how loops differ across languages.
How to read this tab: The Python-specific loop syntax is covered at Control Flow & Loops. This page explains why loops exist and how the same concept appears in every language.
A loop is a set of instructions written once that the computer follows multiple times — like a recipe step that says "repeat until done."
Python's for is a for-each, not a counting loop. C's for (int i=0; i<n; i++) is a counting loop. Python's for item in collection iterates over any iterable directly. To count in Python, use range(): for i in range(n). This is more readable and less error-prone — no off-by-one errors from < vs <=.
The for loop: repeat a fixed number of times
A for loop repeats a block of code a specific number of times, or once for each element in a sequence. In Python, for i in range(5) runs the loop body 5 times, with i taking values 0, 1, 2, 3, 4.
# Count from 0 to 4
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# Iterate over a list
fruits = ["mango", "apple", "banana"]
for fruit in fruits:
print(fruit) # mango, apple, banana
# C-style for loop equivalent in Python using range
for i in range(0, 10, 2): # start=0, stop=10, step=2
print(i) # 0, 2, 4, 6, 8
While loops risk infinite loops. A for loop terminates when the collection runs out. A while loop only terminates if the condition eventually becomes False. Always verify that something inside the loop modifies the condition. The classic infinite loop: while True: — only safe with a break statement inside.
The while loop: repeat until a condition changes
A while loop runs as long as a condition is true. Use it when you don't know in advance how many times you'll need to repeat — for example, keep asking for input until the user enters something valid.
total = 0
number = 1
while number <= 100: # repeat while true
total += number
number += 1 # update: without this, infinite loop!
print(total) # 5050 — sum of 1 to 100
For-each is the safest loop. It can't go out of bounds (no index arithmetic). It works on any iterable — lists, strings, files, generators, dict keys. When you need the index too, use enumerate(): for i, item in enumerate(collection). When you need to iterate two sequences together, use zip(): for a, b in zip(list1, list2).
For-each: iterating collections
Modern languages provide a clean syntax for iterating every element of a collection without managing an index counter. Python's for item in collection is idiomatic. Java has for (Item item : collection). JavaScript has for (const item of array). This is the preferred loop in most cases when you need every element — it's simpler and harder to get wrong than index-based loops.
Python's loop else is unusual and powerful. A for/while loop can have an else clause that runs only if the loop completed without hitting break. This is perfect for "search and not found" patterns: for item in collection: if matches(item): found = item; break; else: found = None. The else means "the break was never hit."
Break and continue
break exits the loop immediately, regardless of whether the condition is still true. continue skips the rest of the current iteration and jumps to the next one.
numbers = [3, 7, 2, 9, 1, 5]
# break: stop when we find 9
for n in numbers:
if n == 9:
print("Found 9!")
break
print(n) # 3, 7, 2, then stops
# continue: skip even numbers
for n in range(10):
if n % 2 == 0:
continue # skip even numbers
print(n) # 1, 3, 5, 7, 9
Loop patterns, Big O, enumerate, and zip
What you'll learn: Common loop patterns and when to use each. How nested loops affect time complexity (Big O). Python's enumerate() and zip() for cleaner loop code.
How to read this tab: The Big O section connects to algorithm complexity — essential background for technical interviews and understanding why some code is slower than others.
The same loop ideas appear in every language — syntax only varies. Python's for x in list is JavaScript's for (const x of list) is Java's for (Type x : list) is Go's for _, x := range list. Learn the concept once; the syntax adaptation is trivial.
Loop patterns across languages
C (index-based for):
for (int i = 0; i < 10; i++) { printf("%d ", i); }
Java (for-each):
for (String s : list) { System.out.println(s); }
JavaScript (for...of):
for (const item of array) { console.log(item); }
Rust (range):
for i in 0..10 { println!("{}", i); }
Go:
for i := 0; i < 10; i++ { fmt.Println(i) } // C-style
for _, v := range slice { fmt.Println(v) } // range-based
Python do-while equivalent (Python has no do-while):
while True:
data = input("Enter value: ")
if data: break
Nested loops multiply complexity. One loop over n items is O(n). Two nested loops are O(n²). Three nested loops are O(n³). This matters enormously for performance: O(n²) with n=10,000 is 100 million operations. Before writing a nested loop, ask: is there a data structure (dict/set) that solves this in O(n) instead?
Nested loops and Big O
Loops are the primary source of algorithmic complexity. A single loop over n elements is O(n). Two nested loops over n elements is O(n²). Three levels of nesting is O(n³). Understanding loop complexity is the foundation of algorithm analysis — recognising when a nested loop can be replaced by a more efficient approach (sorting, hashing, binary search) is a core programming skill.
n = 1000
# O(n) — one loop
for i in range(n):
pass # executes n times
# O(n²) — nested loop
for i in range(n):
for j in range(n):
pass # executes n*n = 1,000,000 times
# O(n log n) — halving inner range
for i in range(n):
j = n
while j > 0:
j //= 2 # executes n * log₂(n) ≈ 10,000 times
enumerate() and zip() make Python loops more readable and less error-prone. Never write for i in range(len(items)): item = items[i] — write for i, item in enumerate(items). zip() stops at the shorter sequence — use itertools.zip_longest() if you need to handle unequal lengths.
Enumerate and zip
Two built-in patterns eliminate the need for manual index counters in Python: enumerate(iterable) yields (index, value) pairs — use when you need both the index and the value. zip(a, b) iterates two sequences in parallel — use when you need corresponding elements from multiple collections. Both are more readable and less error-prone than index-based loops.
for (int i = 0; i < n; i++) is an index-based counting loop. A for-each loop (for item in collection) iterates a collection without an index. Modifying a collection's length while iterating it with a for-each loop is undefined in some languages (ConcurrentModificationException in Java) and silently wrong in others.while True:) that wait for and process events. The break statement inside the loop provides the controlled exit.break only exits the innermost loop. In a nested loop, break exits only the loop that directly contains it. To exit an outer loop from inside an inner loop in Python, use a flag variable or restructure as a function with return. Some languages (Java, Kotlin) support labelled breaks to exit a specific outer loop.Loop invariants, loop unrolling, and vectorisation
What you'll learn: Loop invariants for formally proving loop correctness. How compilers optimise loops (unrolling, vectorisation with SIMD instructions). Why NumPy's vectorised operations beat Python loops.
How to read this tab: Read when studying algorithms formally or when optimising numerical code.
Loop invariants and formal correctness
A loop invariant is a condition that holds true at the beginning of every iteration of a loop. Establishing, maintaining, and using a loop invariant is the formal technique for proving that a loop is correct. To prove a loop correct: (1) show the invariant holds before the loop begins (initialisation); (2) show that if it holds at the start of an iteration, it still holds at the end (maintenance); (3) show that when the loop exits, the invariant plus the exit condition gives the desired result (termination). Dijkstra and Hoare formalised this in the 1970s. It is the basis of formal verification and the conceptual foundation of why loop invariants matter in practice.
Loop unrolling and vectorisation
Loop unrolling is a compiler optimisation that replicates the loop body multiple times to reduce loop overhead (branch instruction, counter update, condition check). GCC and Clang apply this at -O2 and above. Vectorisation transforms scalar loops into SIMD (Single Instruction, Multiple Data) instructions that process multiple elements per clock cycle. A loop summing 1000 floats can be vectorised to process 8 floats at a time using AVX2 instructions, reducing 1000 iterations to ~125. Controlled by -O3 and -march=native in GCC/Clang.
Knuth, D. E. (1997). The Art of Computer Programming, Vol. 1: Fundamental Algorithms. Addison-Wesley. — Formal loop analysis and algorithmic complexity. Dijkstra, E. W. (1976). A Discipline of Programming. Prentice Hall. — Loop invariants and formal program correctness.
✅ Expert tab complete
- I can explain what a loop invariant is and why it's useful for proving correctness
- I know why a NumPy vectorised operation is ~100x faster than an equivalent Python loop