Loops: the engine that repeats what the machine needs
Loops: the engine that repeats what the machine needs
In the previous articles in this series we explored two fundamental concepts: the variable, as the minimum unit of state, and the conditional, as the minimum unit of decision. Today we face the third pillar of programming: the loop, the structure that allows a program to repeat an operation without the programmer having to write it over and over again.
Loops are, probably, the concept that most transformed the history of computing. Before them, a program was a linear sequence of instructions executed from start to finish. With them, a program can process millions of data points, traverse complex structures, wait for events, and repeat calculations until a condition is met. Without loops, video games would not exist, browsers could not render pages, servers could not handle multiple requests, and artificial intelligence could not train models.
But what exactly is a loop? How does it work at the lowest level of the machine? Why do so many variants exist and how do they differ? And most importantly for current times: what role do loops play in the era of code agents?
1. What is a loop?
A loop is a control structure that allows a block of code to be executed repeatedly, either a determined number of times, until a condition is met, or while traversing a collection of elements. In essence, it is how we tell the machine: "Repeat this until I tell you to stop."
In most programming languages, loops are implemented through three fundamental mechanisms:
- Counter-controlled iteration: repeats a fixed number of times (for example,
for i = 0; i < 10; i++). - Condition-controlled iteration: repeats while a condition is true (for example,
while (condition)). - Iteration over collections: traverses each element of a data structure (for example,
for element in list).
Each of these mechanisms has its own mental model, its own low-level implementation, and its own use cases.
2. The loop at a low level: the jump that changed everything
To truly understand what a loop is, we must descend to the assembly level and, beyond that, to the hardware level. At bottom, a loop reduces to a comparison operation followed by a conditional jump that points backward in the code.
2.1. The fundamental structure: compare, jump, repeat
In the article on conditionals we saw that the processor uses condition flags (ZF, SF, CF, OF) to decide whether a jump should be taken or not. A loop is nothing more than a conditional that, instead of jumping forward to skip a block of code, jumps backward to repeat it.
The following diagram shows the basic structure of a for loop translated into assembly.
flowchart TD
A["Initialization:i = 0"] --> B["Comparison:CMP i, 10"]
B --> C{"i < 10?"}
C -->|Yes| D["Loop body:execute instructions"]
D --> E["Increment:i = i + 1"]
E --> B
C -->|No| F["Exit the loop:continue with the rest of the program"]
style A fill:#e3f2fd,stroke:#1565c0
style B fill:#fff3e0,stroke:#ef6c00
style D fill:#e8f5e9,stroke:#2e7d32
style F fill:#f3e5f5,stroke:#7b1fa2
In assembly, this loop would look like this in x86-64:
mov ecx, 0 ; i = 0
.loop:
cmp ecx, 10 ; compare i with 10
jge .end ; if i >= 10, exit the loop
; loop body
; ...
inc ecx ; i = i + 1
jmp .loop ; return to the start of the loop
.end:
; continue with the rest of the program
The jmp .loop instruction is the key. It is an unconditional jump that returns execution to the start of the block. The jge .end instruction is a conditional jump that exits the loop when the condition is no longer met.
2.2. The impact on the processor pipeline
In the article on conditionals we explained that modern processors use a pipeline to execute several instructions simultaneously, and that conditional jumps can break that pipeline if the branch predictor fails.
Loops are particularly interesting in this context because they present a highly predictable jump pattern. In most cases, the processor correctly predicts that the backward jump will be taken (because the loop repeats many times) and only fails on the last iteration, when the loop ends.
Modern branch predictors include components specialized in loops, such as Intel's Loop Stream Detector, which detects small loop patterns and executes them directly from an instruction buffer, avoiding the cost of decoding on each iteration.
2.3. Loop unrolling: the optimization that eliminates the loop
One of the most common optimizations performed by compilers is loop unrolling. It consists of replicating the loop body several times in the generated code, reducing the number of jumps and comparisons.
For example, a loop that sums the elements of an array could be unrolled as follows:
; Original loop
mov ecx, 0
.loop:
add eax, [array + ecx*4]
inc ecx
cmp ecx, 1000
jl .loop
; Unrolled loop (4 iterations per turn)
mov ecx, 0
.loop:
add eax, [array + ecx*4]
add eax, [array + (ecx+1)*4]
add eax, [array + (ecx+2)*4]
add eax, [array + (ecx+3)*4]
add ecx, 4
cmp ecx, 1000
jl .loop
Unrolling reduces the number of jumps and comparisons but increases code size. It is a classic optimization technique that remains valid in modern compilers.
3. Types of loops and their differences
Throughout the history of programming, multiple loop variants have emerged, each with its own semantics and its own use cases. The most important are for, for each, while, and do-while.
3.1. for: the loop with an explicit counter
The classic for loop (also called the three-clause for) has the following structure in C, Java, JavaScript, and similar languages:
for (initialization; condition; increment) {
// loop body
}
It is used when the number of iterations is known in advance or when an explicit counter is needed that increments on each turn. It is the most common loop in algorithms that traverse arrays or perform repetitive calculations.
In Python, for has a different syntax, but its semantics are similar when used with range():
for i in range(10):
print(i)
3.2. for each: the loop over collections
The for each loop (also called for-of in JavaScript, foreach in C#, or simply for in Python) traverses the elements of a collection directly, without the need for an explicit counter.
for element in list:
print(element)
for (const element of list) {
console.log(element);
}
foreach (var element in list)
{
Console.WriteLine(element);
}
The fundamental difference from the classic for is that for each does not expose the index of the iteration (at least not directly), and does not allow modifying the collection during traversal in most languages. Internally, for each is implemented through an iterator, which is an object that maintains the state of the traversal and exposes a next() method that returns the next element.
flowchart TD
A["Start of for each"] --> B["Get iterator from the collection"]
B --> C{"Are there more elements?"}
C -->|Yes| D["Get next element"]
D --> E["Execute loop body"]
E --> C
C -->|No| F["End of loop"]
style B fill:#e3f2fd,stroke:#1565c0
style C fill:#fff3e0,stroke:#ef6c00
style E fill:#e8f5e9,stroke:#2e7d32
The main advantage of for each is readability: the code expresses the intention of traversing a collection without being distracted by counter details. The main disadvantage is that the collection cannot be modified during traversal (in most languages) and there is no direct access to the index.
3.3. while: the condition-controlled loop
The while loop evaluates a condition at the start of each iteration. If the condition is true, it executes the loop body and evaluates the condition again. If it is false, it exits the loop.
while (condition) {
// loop body
}
It is used when the number of iterations is not known in advance, and the loop must continue until a specific condition is met (for example, reading a file to the end, waiting for a user response, or processing a message queue).
The key difference from the classic for is that while has no explicit initialization or increment clause. The programmer is responsible for managing the state that controls the condition.
3.4. do-while: the loop that executes at least once
The do-while loop evaluates the condition at the end of each iteration, which guarantees that the loop body executes at least once, regardless of whether the condition is true or false from the start.
do {
// loop body
} while (condition);
It is used when the loop body needs to execute at least once, such as in interactive menus or in reading data that must be validated before continuing.
The fundamental difference from while is the moment the condition is evaluated: while evaluates before executing the body (it may execute zero times), while do-while evaluates after (it executes at least once).
3.5. Comparative table of loop types
| Type | Condition evaluation | Number of iterations | Typical use |
|---|---|---|---|
for |
Before the body | Known or counter-controlled | Traversing arrays with an index, repetitive calculations |
for each |
Before the body | Equal to the number of elements | Traversing collections without modifying them |
while |
Before the body | Unknown (depends on the condition) | Waiting for events, processing queues, reading files |
do-while |
After the body | At least once | Interactive menus, data validation |
4. Loops in different programming languages
Let us see how loops are written in several languages and what particularities each one has.
4.1. C
// classic for
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
// while
int i = 0;
while (i < 10) {
printf("%d\n", i);
i++;
}
// do-while
int j = 0;
do {
printf("%d\n", j);
j++;
} while (j < 10);
C has no native for each. To traverse an array, the classic for with an index is used.
4.2. Java
// classic for
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// for each (enhanced for)
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.println(number);
}
// while
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}
// do-while
int j = 0;
do {
System.out.println(j);
j++;
} while (j < 10);
Java introduced for each (enhanced for) in Java 5 (2004), simplifying the traversal of arrays and collections.
4.3. Python
# for over range (equivalent to classic for)
for i in range(10):
print(i)
# for each over a list
numbers = [1, 2, 3, 4, 5]
for number in numbers:
print(number)
# while
i = 0
while i < 10:
print(i)
i += 1
# do-while does not exist natively in Python
# It is simulated with a while True and a break
i = 0
while True:
print(i)
i += 1
if i >= 10:
break
Python has no native do-while. Python's for is actually a for each that traverses any iterable.
4.4. JavaScript
// classic for
for (let i = 0; i < 10; i++) {
console.log(i);
}
// for each (for-of)
const numbers = [1, 2, 3, 4, 5];
for (const number of numbers) {
console.log(number);
}
// while
let i = 0;
while (i < 10) {
console.log(i);
i++;
}
// do-while
let j = 0;
do {
console.log(j);
j++;
} while (j < 10);
JavaScript also has for-in, which traverses the keys of an object, but it is not a for each in the strict sense.
4.5. Rust
// for over a range
for i in 0..10 {
println!("{}", i);
}
// for each over a collection
let numbers = vec![1, 2, 3, 4, 5];
for number in &numbers {
println!("{}", number);
}
// while
let mut i = 0;
while i < 10 {
println!("{}", i);
i += 1;
}
// loop (infinite loop with break)
let mut j = 0;
loop {
println!("{}", j);
j += 1;
if j >= 10 {
break;
}
}
Rust has no do-while; instead, loop is used with an explicit exit condition.
5. Complete diagram: from source code to hardware
The following diagram summarizes the complete journey of a loop, from source code to execution in the processor.
flowchart TD
A["Source code:for (i = 0; i < 10; i++)"] --> B["Compiler / Interpreter"]
B --> C{"Compiled orinterpreted language?"}
C -->|Compiled C, Rust| D["Assembly code:mov ecx, 0.loop:cmp ecx, 10jge .end...inc ecxjmp .loop"]
C -->|Bytecode Java, Python| E["Bytecode:LOAD_CONST 0STORE_FAST i...JUMP_ABSOLUTE"]
C -->|JIT JavaScript| F["JIT compilationto machine code"]
D --> G["Assemblerto machine code"]
E --> H["Virtual Machineinterprets bytecode"]
F --> G
G --> I["Processor:Fetch, Decode, Execute"]
H --> I
I --> J["Branch Predictor:predicts the loop jump"]
J --> K{"Prediction correct?"}
K -->|Yes| L["Execution continueswithout penalty"]
K -->|No| M["Discard speculativeinstructionsPenalty: 10-20 cycles"]
M --> N["Load correct path"]
L --> O["Final result"]
N --> O
style A fill:#e3f2fd,stroke:#1565c0
style J fill:#fff3e0,stroke:#ef6c00
style M fill:#fce4ec,stroke:#c62828
6. The combination that gives rise to programs
The three concepts we have explored in this series —variables, conditionals, and loops— are not isolated entities. They are the building blocks with which all the programs we use daily are constructed.
A program is, in essence, a sequence of operations that combine these three elements:
- Variables store the program's state.
- Conditionals allow decisions to be made based on that state.
- Loops allow operations to be repeated until the state reaches a desired value.
Let us look at a concrete example. A program that calculates the sum of even numbers from 1 to 100:
sum = 0 # variable
for i in range(1, 101): # loop
if i % 2 == 0: # conditional
sum += i # variable
print(sum)
This program combines the three concepts: a variable (sum), a loop (for), and a conditional (if). The result is 2550, but what matters is not the result but the structure: the loop iterates over a range, the conditional filters the even numbers, and the variable accumulates the sum.
This same pattern is repeated in all programs, from the simplest to the most complex. A web browser traverses the nodes of the DOM (loop), checks whether each node meets certain conditions (conditional), and updates the page state (variable). A video game iterates over the objects in the scene (loop), checks for collisions (conditional), and updates positions (variable). A web server iterates over client requests (loop), checks whether the request is valid (conditional), and generates a response (variable).
In the era of code agents, these concepts remain fundamental. An AI agent that generates code must understand the structure of loops to produce correct and efficient code. And developers who work with agents must know how to read and write loops to review and modify the generated code.
7. Conclusion
The loop is much more than a keyword in a programming language. It is the expression of one of the most fundamental operations in computing: the ability to repeat an operation. From conditional jumps in assembly to high-level iterators, every loop is a small repetition machine that allows programs to process large amounts of data without the programmer having to write every step manually.
The different types of loops —for, for each, while, do-while— are not arbitrary variants. Each has a specific semantics that makes it suitable for a particular type of problem. Understanding that semantics is what distinguishes a programmer who writes correct code from one who writes elegant code.
And when we combine loops with variables and conditionals, we obtain the fundamental triad of programming. With those three elements, all programs are built, from the simplest to the most complex. And in that simplicity lies its greatness.
8. References
- Hennessy, J. L., Patterson, D. A., & Kozyrakis, C. (2025). Computer architecture: A quantitative approach (7th ed.). Morgan Kaufmann.
- Mittal, S. (2018). A survey of techniques for dynamic branch prediction. Concurrency and Computation: Practice and Experience, 30(1), e4707. https://doi.org/10.1002/cpe.4707
- Intel Corporation. (2024). Intel 64 and IA-32 architectures optimization reference manual. Intel.
- ECMA International. (2025). ECMAScript 2025 language specification (15th ed.). https://www.ecma-international.org
- Python Software Foundation. (2026). The Python language reference (Version 3.13). https://docs.python.org/3/reference/
- Oracle. (2026). The Java Virtual Machine specification. https://docs.oracle.com/javase/specs/
- Rust Team. (2026). The Rust programming language. https://doc.rust-lang.org/book/
- Stallman, R. M., & GCC Developer Community. (2025). GNU Compiler Collection (GCC) internals: Loop optimization. Free Software Foundation. https://gcc.gnu.org/onlinedocs/gccint/
Loading reactions...
Comments (0)
Loading session...
No comments yet. Be the first to comment.