Loops in C — theory with for, while and do…while counting the bus stops (and the ; that only stopped at stop 6)
I was coming back from uni on the number 12 bus and, to kill time, I wanted to write the silliest program in the world: print the five stops on the route, one per line. A textbook for. I compiled it on my laptop, ran it and got this:
#include <stdio.h>
int main(void) {
int stop;
printf("Stops on line 12:\n");
for (stop = 1; stop <= 5; stop++);
{
printf("Stop %d\n", stop);
}
printf("End of route\n");
return 0;
}
Output:
Stops on line 12: Stop 6 End of route
One single stop, and it was stop 6, which does not even exist. Five minutes staring at the code until I saw it: a ; at the end of the for line. That semicolon is an empty body. The loop did its five laps doing nothing, stop ended up as 6 and the block in braces ran once, already outside the loop. gcc had warned me, by the way, and I did not read it:
hook.c: In function 'main': hook.c:7: warning: this 'for' clause does not guard... [-Wmisleading-indentation] hook.c:8: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'
In this article we cover the three loops in C (for, while and do…while), the order in which each part runs, what the variable is worth when the loop ends, break, continue, nested loops and the mistakes that come up again and again in IC2: the ; from the story, the extra lap caused by <=, the scanf that leaves a loop spinning forever and the break that does not leave where you thought. We come from conditionals in C, where the bus ticket machine could only serve one rider; here it learns to serve all of them. And if you want the complete programs, they are in the loops in C practice.
The bus lines, stops and fares in this topic are made up for the example. Every program has been compiled with gcc -Wall -std=c11 (gcc 14) and the outputs are the real ones. If you have not set up your environment yet, follow the gedit and gcc on Fedora guide, or try them straight away in the site’s C playground.
Table of Contents
The for loop in C
In Python, for x in list walks through a sequence. In C there are no sequences to walk through: for is a counter loop with three parts separated by ; inside the parentheses:
for (initialisation; condition; update) {
// body: what gets repeated
}
- Initialisation: runs only once, before starting.
- Condition: checked before every lap. If it is true (non-zero), the body runs; if it is false, the loop ends.
- Update: runs at the end of every lap, after the body.
Let’s see it on the bus: four stops, and three riders get on at each one.
#include <stdio.h>
int main(void) {
int stop;
int riders = 0; // on board when leaving the first stop
for (stop = 1; stop <= 4; stop++) {
riders += 3; // 3 get on at every stop
printf("Stop %d: %2d riders on board\n", stop, riders);
}
// The for has finished: stop kept the value that made the condition false
printf("On leaving the for, stop is %d\n", stop);
return 0;
}
Output:
Stop 1: 3 riders on board Stop 2: 6 riders on board Stop 3: 9 riders on board Stop 4: 12 riders on board On leaving the for, stop is 5
The last line is the one that surprises people the first time. Why is stop 5 if there are only four stops? Because the for does not end when the fourth lap finishes, but when the condition is false. After lap 4, the update sets stop to 5, the condition asks 5 <= 4, the answer is No and the loop ends. The drawing shows it step by step:
Checking it by hand: the condition is evaluated with 1, 2, 3, 4 (four Yeses) and with 5 (the No). Five checks for four laps, always one more. And the riders: 3 + 3 + 3 + 3 = 12.
| Lap | stop at the start | stop <= 4? | riders after the body | stop after stop++ |
|---|---|---|---|---|
| 1 | 1 | Yes | 3 | 2 |
| 2 | 2 | Yes | 6 | 3 |
| 3 | 3 | Yes | 9 | 4 |
| 4 | 4 | Yes | 12 | 5 |
| — | 5 | No: leaves | 12 | — |
Python range() equivalents in C
If you come from Python (from the Python loops topic), this table saves you thinking. Each range translates by changing the start, the condition and the step:
#include <stdio.h>
int main(void) {
int i;
printf("range(5) -> ");
for (i = 0; i < 5; i++) printf("%d ", i); // 0 1 2 3 4
printf("\nrange(1, 6) -> ");
for (i = 1; i <= 5; i++) printf("%d ", i); // 1 2 3 4 5
printf("\nrange(0, 10, 2) -> ");
for (i = 0; i < 10; i += 2) printf("%d ", i); // in steps of 2
printf("\nrange(5, 0, -1) -> ");
for (i = 5; i > 0; i--) printf("%d ", i); // backwards
printf("\n");
return 0;
}
Output:
range(5) -> 0 1 2 3 4 range(1, 6) -> 1 2 3 4 5 range(0, 10, 2) -> 0 2 4 6 8 range(5, 0, -1) -> 5 4 3 2 1
| Python | C | Watch out for |
|---|---|---|
range(5) | for (i = 0; i < 5; i++) | The end is not included: < |
range(1, 6) | for (i = 1; i <= 5; i++) | To include 5, <= |
range(0, 10, 2) | for (i = 0; i < 10; i += 2) | The step goes in the update |
range(5, 0, -1) | for (i = 5; i > 0; i--) | Backwards: > and i-- |
If the loop has to go backwards, the comparison (> or >=) and the update (i--) change together. If you only change one, it either never goes in or never comes out.
Variable scope in the for loop
In old C (C89) the loop variable had to be declared before the for. Since C99 it can be declared inside, and with gcc -std=c11 (what we use throughout the course) it simply works:
int i; // outside: still alive after the for
for (i = 0; i < 3; i++) { }
for (int j = 0; j < 3; j++) { } // inside: only exists in the for
The difference is not about style: it changes where the variable exists. If you declare it inside and try to use it afterwards, it does not compile:
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) { // i only exists inside the for
printf("lap %d\n", i);
}
printf("i on exit: %d\n", i); // i no longer exists here
return 0;
}
scope.c: In function 'main': scope.c:7: error: 'i' undeclared (first use in this function) scope.c:7: note: each undeclared identifier is reported only once for each function it appears in
So which one should you use? If after the loop you need to know where it stopped (like in the IC2 lab sessions, which move a pointer back with asientos = asientos - i), declare it outside. Otherwise, inside: that way nobody uses it by mistake.
What the variable is worth on exit: < vs <=
This is one of the mistakes that costs the most marks in IC2, because the program compiles and almost works. Look at three similar loops and how many laps each one does:
#include <stdio.h>
int main(void) {
int i;
int laps;
laps = 0;
for (i = 0; i < 5; i++) laps++;
printf("i = 0; i < 5 : %d laps, i ends at %d\n", laps, i);
laps = 0;
for (i = 0; i <= 5; i++) laps++; // one lap too many
printf("i = 0; i <= 5 : %d laps, i ends at %d\n", laps, i);
laps = 0;
for (i = 1; i <= 5; i++) laps++;
printf("i = 1; i <= 5 : %d laps, i ends at %d\n", laps, i);
return 0;
}
Output:
i = 0; i < 5 : 5 laps, i ends at 5 i = 0; i <= 5 : 6 laps, i ends at 6 i = 1; i <= 5 : 5 laps, i ends at 6
With i = 0; i < 5 there are five laps (0, 1, 2, 3 and 4). With i = 0; i <= 5, six: 5 also goes in. And with i = 1; i <= 5 it is five again, but i ends at 6, not 5. The rule for counting laps without getting it wrong: with <, it is end − start (5 − 0 = 5); with <=, one more (5 − 1 + 1 = 5; 5 − 0 + 1 = 6). And on exit, the variable is always the first value that made the condition false.
Writing <= when you meant < (or the other way round) gives one lap too many or too few, and the compiler does not warn you. Count the laps with the rule above before compiling, especially when the loop walks through an array: in the pointers topic you will see that the extra lap reads memory that is not yours.
The increment operators — i++, ++i, i += n
The update is almost always i++, but it is worth knowing exactly what it does, because ++ also shows up outside for loops:
#include <stdio.h>
int main(void) {
int a = 5, b = 5;
int x = a++; // x gets 5 and THEN a becomes 6
int y = ++b; // b becomes 6 and THEN y gets 6
int k;
printf("x = %d, a = %d\n", x, a);
printf("y = %d, b = %d\n", y, b);
// In a for update, i++ and ++i are the same: the value is not used
for (k = 0; k < 10; k += 3) {
printf("%d ", k);
}
printf("\n");
return 0;
}
Output:
x = 5, a = 6 y = 6, b = 6 0 3 6 9
a++ (post-increment) hands over the old value and then adds: x gets 5 and a ends at 6. ++b (pre-increment) adds first and hands over the new value: y gets 6. In a for update the value handed over is not used, so i++ and ++i do the same. For steps other than 1, i += 3 (0, 3, 6, 9) or i -= 2.
Practical for examples: the accumulator pattern
Three uses you will write a thousand times: repeat something a fixed number of times, accumulate and count backwards.
#include <stdio.h>
int main(void) {
int i;
int sum = 0;
// 7 times table: fixed number of laps, a textbook for
printf("7 times table:\n");
for (i = 1; i <= 10; i++) {
printf("7 x %2d = %2d\n", i, 7 * i);
}
// Sum of 1 to 100: the accumulator starts at 0 BEFORE the loop
for (i = 1; i <= 100; i++) {
sum += i;
}
printf("\nSum 1-100: %d\n", sum);
// Countdown: start at the top and subtract
printf("\nCountdown: ");
for (i = 10; i >= 1; i--) {
printf("%d ", i);
}
printf("Go!\n");
return 0;
}
Output:
7 times table: 7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 7 x 4 = 28 7 x 5 = 35 7 x 6 = 42 7 x 7 = 49 7 x 8 = 56 7 x 9 = 63 7 x 10 = 70 Sum 1-100: 5050 Countdown: 10 9 8 7 6 5 4 3 2 1 Go!
The sum from 1 to 100 is 5050, which you can check with Gauss’s formula: 100 · 101 / 2 = 5050. Notice that sum = 0 goes before the loop: if you put it inside, it would reset on every lap and you would end up with 100. And in the table, %2d reserves two spaces so that 7 and 70 line up.
The while loop in C
while is the simplest one: it checks the condition before every lap and repeats while it is true. It has no initialisation or update of its own; you write those yourself.
while (condition) {
// body
// something here has to end up changing the condition
}
Compared with Python, only the shape changes: compulsory parentheses and braces instead of indentation. Its place is when you do not know how many laps there will be. For example, the driver types the age of every rider who gets on until they type 0:
#include <stdio.h>
int main(void) {
int age;
int sum = 0;
int count = 0;
printf("Riders' ages (0 to finish):\n");
// Read in the condition itself: if scanf reads no number or 0 arrives, it stops
while (scanf("%d", &age) == 1 && age != 0) {
sum += age;
count++;
}
if (count > 0) {
printf("Riders: %d\n", count);
printf("Average age: %.2f\n", (double)sum / count); // without (double), integer division
} else {
printf("Nobody got on\n");
}
return 0;
}
With 22, 67, 15 and 0 as input:
Riders' ages (0 to finish): 22 67 15 0 Riders: 3 Average age: 34.67
The condition does two things at once: scanf("%d", &age) == 1 reads a number and checks it managed to (scanf returns how many items it read), and age != 0 checks whether it is the sentinel. Since && short-circuits, if scanf fails age is not even looked at. The sums: 22 + 67 + 15 = 104 and 104 / 3 = 34.67. The (double) is essential: without it, 104 / 3 is integer division and would give 34.
The special value that marks the end of the data (here, 0) is called a sentinel. Pick one that cannot be real data: an age of 0 makes no sense, so it works.
When to use for, while or do…while
Any for can be written as a while and vice versa. These two do exactly the same:
for (i = 0; i < 5; i++) { i = 0;
printf("%d ", i); while (i < 5) {
} printf("%d ", i);
i++;
}
So why are there two? Readability: for puts everything that controls the loop on one line, and while makes it clear that you do not know when you will stop.
| Situation | Loop | Why |
|---|---|---|
| You know how many laps: 10 stops, 30 students, from 1 to n | for | Start, end and step on one single line |
| Repeat until a special value arrives (0, a letter) | while | You do not know how many times |
| Ask for a value and repeat while it is wrong | do…while | It has to be asked at least once |
| Repeat until something happens (guessing, filling the bus) | while with a flag or while (1) with break | The way out is inside the body |
The do…while loop — exclusive to C (and Java)
do…while runs the body first and checks the condition afterwards. That is why it always does at least one lap:
do {
// body: runs at least once
} while (condition); // here the ; is compulsory
The difference is very clear with a condition that is already false from the start:
#include <stdio.h>
int main(void) {
int x = 10;
printf("while:\n");
while (x < 5) { // checked BEFORE: 10 < 5 is false
printf(" inside the while\n");
}
printf("do...while:\n");
do {
printf(" inside the do...while\n");
} while (x < 5); // checked AFTER: it has already gone in once
return 0;
}
Output:
while: do...while: inside the do...while
while checks 10 < 5, sees it is false and never goes in. do…while goes in, prints and then asks. The drawing puts the three loops side by side: notice where the condition diamond is in each one.
If you want to see the three loops drawn with diamonds and arrows, the way the exam asks for them, there is a whole topic on flowcharts in C.
Is there a do while in Python? No. In Python it is imitated with while True: and a break at the end of the body. In C you can write it like that too (while (1) with break), but do…while says it more clearly.
The perfect use for do…while: validating input
To ask for a value you have to ask at least once, and repeat while it is wrong. That is exactly the shape of do…while:
#include <stdio.h>
int main(void) {
float grade;
do {
printf("Enter a grade (0-10): ");
scanf("%f", &grade);
if (grade < 0 || grade > 10) {
printf("Error: the grade must be between 0 and 10\n");
}
} while (grade < 0 || grade > 10); // repeats WHILE the input is bad
printf("Valid grade: %.2f\n", grade);
return 0;
}
With -5, 15 and 7.5 as input:
Enter a grade (0-10): -5 Error: the grade must be between 0 and 10 Enter a grade (0-10): 15 Error: the grade must be between 0 and 10 Enter a grade (0-10): 7.5 Valid grade: 7.50
The condition of the final while is the bad-input condition: grade < 0 || grade > 10. While it is true, it repeats. Without do…while you would have to write the printf and the scanf twice, once before the loop and once inside.
Now, the IC2 trap. What happens if instead of a number someone types the word seven? This:
Enter a grade (0-10): seven Error: the grade must be between 0 and 10 Enter a grade (0-10): Error: the grade must be between 0 and 10 Enter a grade (0-10): Error: the grade must be between 0 and 10 Enter a grade (0-10): Error: the grade must be between 0 and 10 [emergency brake: 4 laps]
I added a 4-lap emergency brake (the laps lines) so the article is not infinite; without it, the program prints the error non-stop until you press Ctrl+C. Why does it not wait for you to type something else? Because scanf("%f") cannot read seven as a number, returns 0 and leaves the letters in the input. On the next lap it finds the same letters, fails again, and so on forever. Besides, grade does not change, so the condition is still true.
Using scanf inside a loop without checking what it returns. With a number it works; with a letter, infinite loop. Always check scanf(...) == 1 (or the number of items you expect).
The safe version checks what scanf returns and, if it fails, throws away the rest of the line:
#include <stdio.h>
int main(void) {
float grade = -1;
int read_count; // how many items scanf has read
int c;
do {
printf("Enter a grade (0-10): ");
read_count = scanf("%f", &grade);
if (read_count != 1) {
printf("Error: that is not a number\n");
while ((c = getchar()) != '\n' && c != EOF); // empties the line; the ; is the body
grade = -1; // forces another lap
} else if (grade < 0 || grade > 10) {
printf("Error: the grade must be between 0 and 10\n");
}
} while (grade < 0 || grade > 10);
printf("Valid grade: %.2f\n", grade);
return 0;
}
With seven, 12 and 7.5:
Enter a grade (0-10): seven Error: that is not a number Enter a grade (0-10): 12 Error: the grade must be between 0 and 10 Enter a grade (0-10): 7.5 Valid grade: 7.50
The key line is while ((c = getchar()) != '\n' && c != EOF);. It reads character by character up to the Enter (or the end of the input) and does nothing else: its body is the ;. Yes, the same ; from the story, but this time on purpose: here all the work is done by the condition. When you write one like this, leave a comment so nobody thinks it is a mistake.
break and continue in C
breakends the loop immediately and jumps to the first statement after it.continueends only the current lap and jumps to the next one. In afor, “the next one” starts with the update (i++) and then the condition.
Line 12 goes through stops 1 to 10, but stop 4 has roadworks and the shift ends at 8:
#include <stdio.h>
int main(void) {
int stop;
printf("Line 12, stops 1 to 10:\n");
for (stop = 1; stop <= 10; stop++) {
if (stop == 4) {
printf(" %d: roadworks, no stop\n", stop);
continue; // jumps to stop++ and goes on with 5
}
if (stop == 8) {
printf(" %d: end of shift\n", stop);
break; // leaves the for: 9 and 10 are never visited
}
printf(" %d: opens the doors\n", stop);
}
printf("Last stop visited: %d\n", stop);
return 0;
}
Output:
Line 12, stops 1 to 10: 1: opens the doors 2: opens the doors 3: opens the doors 4: roadworks, no stop 5: opens the doors 6: opens the doors 7: opens the doors 8: end of shift Last stop visited: 8
At 4, continue skips the “opens the doors” printf and the lap carries on with stop++, so 5 arrives normally. At 8, break leaves the for: 9 and 10 are never visited and stop stays at 8 (not 11, which is what would have happened if the loop had ended through the condition).
Now the trap: the same program with while. A while has no automatic update: you write stop++ yourself at the end of the body… and continue skips it.
#include <stdio.h>
int main(void) {
int stop = 1;
int laps = 0;
while (stop <= 10) {
if (++laps > 8) { // emergency brake (only for the article)
printf("[emergency brake: 8 laps]\n");
break;
}
if (stop == 4) {
printf(" %d: roadworks, no stop\n", stop);
continue; // ALSO skips the stop++ below
}
printf(" %d: opens the doors\n", stop);
stop++;
}
return 0;
}
Output (again with an emergency brake, of 8 laps):
1: opens the doors 2: opens the doors 3: opens the doors 4: roadworks, no stop 4: roadworks, no stop 4: roadworks, no stop 4: roadworks, no stop 4: roadworks, no stop [emergency brake: 8 laps]
The bus gets stuck at 4 forever: every lap does continue before reaching stop++, so stop never gets past 4.
Using continue in a while whose step is at the end of the body. It does not happen in a for (i++ runs anyway), but it does in a while. Fix: step before the continue, or turn the loop into a for.
Nested loops — a loop inside another
A loop inside another: for every lap of the outer one, the inner one does all of its laps. It is what you use for tables, matrices and, in general, everything with rows and columns.
#include <stdio.h>
int main(void) {
int i, j;
// The outer for picks the row; the inner one walks through that row's columns
for (i = 1; i <= 5; i++) {
for (j = 1; j <= 5; j++) {
printf("%4d", i * j); // %4d: each number takes 4 spaces
}
printf("\n"); // end of row: outside the inner for
}
return 0;
}
Output:
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
Five rows by five columns: the inner printf runs 5 · 5 = 25 times and printf("\n") only 5, because it is outside the inner for. If you put it inside, every number would come out on its own line.
break only leaves the inner loop
Let’s look for the first free seat on the bus (seat 3 in row 2) with two loops and a break:
#include <stdio.h>
int main(void) {
int row, seat;
// We look for the first free seat on the bus: seat 3 in row 2
for (row = 1; row <= 3; row++) {
for (seat = 1; seat <= 4; seat++) {
printf("(%d,%d) ", row, seat);
if (row == 2 && seat == 3) {
printf("<- free ");
break; // only leaves the seats for
}
}
printf("\n");
}
return 0;
}
Output:
(1,1) (1,2) (1,3) (1,4) (2,1) (2,2) (2,3) <- free (3,1) (3,2) (3,3) (3,4)
Row 3 is walked through completely! break leaves the seats for, but the rows one carries on as if nothing happened. To stop both, the clean way in C is a flag that both conditions check:
#include <stdio.h>
int main(void) {
int row, seat;
int found = 0; // flag: 1 when the seat turns up
int free_row = 0, free_seat = 0;
for (row = 1; row <= 3 && !found; row++) {
for (seat = 1; seat <= 4 && !found; seat++) {
printf("(%d,%d) ", row, seat);
if (row == 2 && seat == 3) {
found = 1; // stops both loops at their next check
free_row = row; // saved: row and seat are still going to change
free_seat = seat;
}
}
printf("\n");
}
printf("Free: row %d, seat %d\n", free_row, free_seat);
printf("On exit: row = %d, seat = %d\n", row, seat);
return 0;
}
Output:
(1,1) (1,2) (1,3) (1,4) (2,1) (2,2) (2,3) Free: row 2, seat 3 On exit: row = 3, seat = 4
The two grids, with what is really visited in each version:
Look at the last line of the output: on exit, row is 3 and seat is 4. When found becomes 1, each for still runs its update before checking the condition (seat++ and then row++). That is why we save free_row and free_seat the moment we find it: it is the same thing as before, the variable on exit is “one more”.
Two variables in one for: the comma
The IC2 lab sessions have something like this: for (pv = v, i = 0; pv <= &v[n-1]; pv++, i++). The comma lets you set up and move two variables at once:
#include <stdio.h>
int main(void) {
int i, j;
// Two variables in one for: set up and moved on, separated by commas
for (i = 0, j = 10; i < j; i++, j -= 2) {
printf("i = %d, j = %2d\n", i, j);
}
printf("On exit: i = %d, j = %d\n", i, j);
return 0;
}
Output:
i = 0, j = 10 i = 1, j = 8 i = 2, j = 6 i = 3, j = 4 On exit: i = 4, j = 2
i goes up 1 at a time and j goes down 2 at a time until they cross: with i = 4 and j = 2, 4 < 2 is false and the loop ends. Careful: the comma only works in the initialisation and the update. In the condition it does not mean “and”: that is what && is for.
Infinite loops and how to get out
A loop whose condition never becomes false is infinite. Sometimes it is a mistake and sometimes it is exactly what you want, as long as there is a break inside:
#include <stdio.h>
int main(void) {
int on_board = 0;
while (1) { // 1 is always true: infinite loop on purpose
on_board += 20;
printf("20 get on: %d on board\n", on_board);
if (on_board >= 60) {
printf("Bus full\n");
break; // the only way out
}
}
return 0;
}
Output:
20 get on: 20 on board 20 get on: 40 on board 20 get on: 60 on board Bus full
while (1) and for (;;) are the two usual ways of writing it (in a for, an empty condition counts as true). They are useful when the way out is decided inside the body: a menu that ends with option 0, a ticket machine that closes at the end of the day… In the IC2 theory you will also see busy waiting, while (turno != 1) {}: an empty loop on purpose that spins until another process changes turno.
If a program hangs in an infinite loop, press Ctrl+C in the terminal to stop it. And to study what it does without flooding the screen, give it a brake like the ones in this article: a counter and a break when it goes past a few laps.
The most common mistakes in IC2
Mistake 1 — Semicolon after the for
It is the one from the story. ; is an empty statement, and the for takes it as its body. If the next line is also indented as if it belonged to the loop, gcc warns with -Wall:
#include <stdio.h>
int main(void) {
int stop;
for (stop = 1; stop <= 5; stop++);
printf("Stop %d\n", stop);
return 0;
}
indent.c: In function 'main': indent.c:6: warning: this 'for' clause does not guard... [-Wmisleading-indentation] indent.c:7: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'
Output:
Stop 6
That warning, -Wmisleading-indentation, is your friend: it says “this for does not guard the line below even though it looks like it”. Do not ignore it. With while (cond); it is even worse: if the condition is true, the program hangs without printing anything.
Mistake 2 — Not updating the variable in a while
In a while you write the update yourself, and if you forget it the condition never changes:
#include <stdio.h>
int main(void) {
int stop = 1;
int laps = 0;
while (stop <= 3) {
printf("Stop %d\n", stop);
// stop++ is missing: the condition never changes
if (++laps == 5) { // emergency brake (only for the article)
printf("[emergency brake: 5 laps]\n");
break;
}
}
return 0;
}
Output (with a brake):
Stop 1 Stop 1 Stop 1 Stop 1 Stop 1 [emergency brake: 5 laps]
Before accepting a while, look in the body for the line that brings the condition closer to being false. If it is not there, it is infinite.
Mistake 3 — Wrong condition in a do…while
To validate “the grade must be between 0 and 10”, the condition to repeat is “it is less than 0 or greater than 10″. With && you ask for both at the same time, and that never happens:
#include <stdio.h>
int main(void) {
float grade;
do {
printf("Enter a grade (0-10): ");
scanf("%f", &grade);
} while (grade < 0 && grade > 10); // no grade meets both at once
printf("Grade accepted: %.2f\n", grade);
return 0;
}
With -5:
Enter a grade (0-10): -5 Grade accepted: -5.00
It accepts a -5 without complaining. -5 < 0 is true, but -5 > 10 is false, and true && false is false: the loop ends the first time. Trick to get it right: write the good-input condition first (grade >= 0 && grade <= 10) and negate it: !(grade >= 0 && grade <= 10), which is the same as grade < 0 || grade > 10.
Mistake 4 — One lap too many: < vs <=
We saw it with on_exit.c: i = 0; i <= 5 does six laps, not five. If the loop counts stops, it prints one that does not exist; if it walks through an array of 5 elements, it reads the sixth, which is not yours. Count the laps before compiling and check the last value that goes in.
| Symptom | Likely cause | ||
|---|---|---|---|
| The body runs only once and with an odd value (6, 11…) | ; right after for (...) | ||
| The program hangs without printing anything | ; after while (...) | ||
| The same line repeats non-stop | The update is missing, or a continue skips it | ||
A scanf error repeats without waiting for you to type | scanf failed (letters) and you did not empty the input | ||
| It accepts values it should reject | && where ` | should be in the do…while` condition | |
| One lap too many or too few | < vs <=, or starting at 0 vs 1 |
Watch it step by step in Python Tutor
Python Tutor (pythontutor.com) runs the code line by line and shows the value of each variable at every step. Choose C as the language and paste this program, which combines a continue and a break:
#include <stdio.h>
int main(void) {
int stop;
int riders = 0;
for (stop = 1; stop <= 6; stop++) {
if (stop == 3) {
continue; // nobody gets on at 3
}
if (stop == 5) {
break; // 5 is the last one of the shift
}
riders += stop; // as many get on as the stop number
}
printf("stop = %d, riders = %d\n", stop, riders);
return 0;
}
Output:
stop = 5, riders = 7
What to watch step by step:
- When the
forstarts,stop = 1runs only once: you will see the arrow never goes back to that part. - In the lap with
stop = 3,continuejumps straight tostop++:ridersdoes not change (it stays at 3). - With
stop = 5,breakleaves the loop before adding:ridersstays at 1 + 2 + 4 = 7. - At the end,
stopis 5, not 7: the loop ended through thebreak, not through the condition.
Quick summary
| Loop | When | Minimum laps | How it is written |
|---|---|---|---|
for | Known number of laps | 0 | for (i = 0; i < n; i++) { … } |
while | Until something happens, not knowing when | 0 | while (cond) { … } |
do…while | Asking for and validating input | 1 | do { … } while (cond); |
break | Leave the loop (only the innermost one) | — | if (…) break; |
continue | Skip to the next lap | — | if (…) continue; |
while (1) / for (;;) | The way out is decided inside | — | With a break inside |
Three ideas to take away:
- The condition is checked once more than there are laps. On exit, the variable is the first value that made it false:
stopends at 5 withstop <= 4. - A
;afterfor (...)orwhile (...)is an empty body. It compiles, and if gcc warns you with-Wmisleading-indentation, listen to it. - Every
scanfinside a loop gets checked (== 1) and, if it fails, the input gets emptied. Otherwise one letter leaves it spinning forever.
Now it is time to write whole programs: in the loops in C practice the ticket machine serves several riders, we calculate series and the number guessing game finally has several attempts. And in the next topic, pointers in C, loops walk through memory for real.
Next step: practiceLoops in C — practice with the bus ticket machine, number series and a 7-attempt guessing game (and the cent the machine kept overcharging me)Did this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)