Loops as Backward Jumps
At the machine level, there is no dedicated 'for' or 'while' construct — a loop is simply a label, a body of instructions, a condition check, and a conditional jump back to the label. A while loop typically checks its condition at the top (test-then-execute), which compiles to a comparison and a forward conditional jump past the body, followed by an unconditional jump back to the check. A do-while loop checks at the bottom (execute-then-test), which needs only a single backward conditional jump after the body, making it marginally more efficient since it avoids one extra jump on every iteration except possibly the last.
Cricket analogy: A bowler running through their over checks after each ball whether six have been bowled before walking back to their mark — that bottom-of-loop check mirrors a do-while loop's single backward jump, more efficient than checking before every ball.
The Dedicated LOOP Instruction
x86 provides a specialized LOOP instruction that implicitly uses ECX as a counter: each execution decrements ECX and jumps to the target label if ECX is not zero, combining decrement and conditional-branch into a single instruction. While convenient for simple fixed-count loops, LOOP is rarely used in modern optimized code because it is often slower than an equivalent DEC/JNZ pair on contemporary CPUs, and it hard-codes ECX, which conflicts with register allocation in more complex functions. Compilers today almost always emit explicit DEC/CMP/Jcc sequences instead.
Cricket analogy: LOOP is like a fixed-over bowling machine that always counts down from a preset number of deliveries and stops automatically — convenient, but a real coach adjusting overs based on match situation (DEC/JNZ with flexible registers) gives finer control.
section .data
sum dd 0
section .text
global _start
_start:
mov ecx, 10 ; loop counter: sum 1..10
mov eax, 0 ; running total
mov ebx, 1 ; current value
sum_loop:
add eax, ebx
inc ebx
dec ecx
jnz sum_loop ; jump back while ecx != 0
mov [sum], eax ; sum now holds 55
mov eax, 1 ; sys_exit
mov ebx, 0
int 0x80Off-by-One Errors and Loop Bounds
Because assembly loop bounds are entirely explicit, off-by-one errors are extremely common: using JLE instead of JL (or vice versa) when comparing an index against an array length shifts the loop by exactly one iteration, either skipping the last valid element or reading one element past the end of the array. This is especially dangerous when iterating over a zero-indexed array of length N, where valid indices run from 0 to N-1; comparing the index against N with JL is correct, but comparing with JLE will access index N, which is out of bounds.
Cricket analogy: Counting overs from over 0 to over 49 in a 50-over match, a scorer who stops the innings after over 50 instead of over 49 has made an off-by-one error, just like using JLE instead of JL against a zero-indexed array bound.
Always double-check whether your loop-bound comparison should be inclusive or exclusive relative to the array or buffer size. Reading or writing one element past the end of a statically allocated array can silently corrupt adjacent memory rather than crashing immediately, making the bug extremely hard to trace back to its source.
Most modern compilers, when targeting performance, transform countdown loops (ECX toward zero) into forms that let a single JNZ handle both the decrement-check and the branch, because comparing against zero is cheaper on many architectures than comparing against an arbitrary bound — this is one reason hand-optimized assembly sometimes counts down even when the source loop counts up.
- A loop in assembly is fundamentally a label, a body, a condition test, and a jump back to the label.
- While loops (test at top) need a forward skip-jump plus a backward loop-jump; do-while loops (test at bottom) need only one backward jump.
- The dedicated LOOP instruction decrements ECX and branches automatically but is rarely used in optimized modern code.
- Compilers favor explicit DEC/CMP/Jcc sequences over LOOP for flexibility and speed.
- Off-by-one errors from using JLE instead of JL (or vice versa) are among the most common assembly bugs.
- Zero-indexed arrays of length N have valid indices 0 to N-1; comparing against N should use JL, not JLE.
- Reading or writing past an array's bound can silently corrupt memory instead of crashing immediately.
Practice what you learned
1. What is the minimum number of jump instructions needed per iteration for a do-while style loop?
2. What register does the x86 LOOP instruction implicitly use as its counter?
3. Why do modern compilers usually avoid the LOOP instruction?
4. For a zero-indexed array of length N, which comparison correctly keeps the loop index within bounds?
5. What is a key risk of writing one element past the end of a statically allocated array in assembly?
Was this page helpful?
You May Also Like
Conditional Jumps
Learn how x86 assembly implements if/else logic using flag-testing conditional jump instructions like JE, JNE, JG, and JL.
The Flags Register
Understand the x86 FLAGS register — the bit fields like ZF, CF, SF, and OF that record the outcome of arithmetic and comparison instructions.
Recursion in Assembly
See how recursive functions are implemented at the machine level using the call stack, and why stack depth and register preservation matter.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics