Loops in C — exercises with primes, rock-paper-scissors, a 3-attempt login and a menu calculator (and the ; that left the list of primes empty)
Revising for the IC2 practical, I tried the primes exercise from memory, without looking at my notes. I compiled it, ran it with 30 and got Total: 0. Not 2, not 3, not 5. My first theory was that the % was wrong; the second, that the break was wrong. It was a ; at the end of the inner for, and the oddest part is that the break, which was written correctly, made the program finish before it even looked at 3. In this article you will see it taken apart step by step, because it is the trickiest loop mistake there is: it compiles and does not do what you read.
You have already seen the loops in C theory and the practice with the ticket machine, the series and the guessing game. Now it is your turn: five exercises on three levels, a “What’s wrong here?” section with three snippets that compile and fail, the commented solutions and a cheat sheet to keep everything at hand.
Table of Contents
How to use this article
- Read the task and the expected output. Your program has to give the same output with the same data.
- Try to solve it in gedit (or in the site’s C playground, typing the data in the Input tab) and compile with
gcc -Wall -std=c11. - If you have been stuck for more than ten minutes, open the hint. Not before.
- Compare with the commented solution, even if yours worked: the difference is often in why it is done that way.
Loops in C exercises — Basic Level
Exercise 1 — Number to word converter
Write a program that asks for a number between 1 and 10 and writes its name (“5 -> five”). It has to repeat until the user types 0, which says goodbye with “Goodbye”. If the number is out of range, or if the user types letters, show an error and ask again. Use a switch inside a do…while.
Expected output (with 5, 3, 15, five and 0):
=== NUMBER TO WORD CONVERTER === Enter a number (1-10, 0 to quit): 5 5 -> five Enter a number (1-10, 0 to quit): 3 3 -> three Enter a number (1-10, 0 to quit): 15 Error: enter a number between 0 and 10 Enter a number (1-10, 0 to quit): five Error: enter a number between 0 and 10 Enter a number (1-10, 0 to quit): 0 Goodbye
0 has to be handled (goodbye) and then end the loop, so the do…while condition is number != 0. For letters, check what scanf returns and, if it is not 1, empty the line with getchar() and put a value in number that falls into the error (for example, -1).
Exercise 2 — Rock Paper Scissors with replays
The player chooses with numbers (1 rock, 2 paper, 3 scissors, 0 quit) and plays as many rounds as they like. The machine does not use rand() (we have not seen it yet): it plays rock, paper, scissors, rock… depending on the round number. A choice outside 0-3 is rejected and does not count as a round. On quitting, the program shows the rounds played and the score.
Expected output (with 2, 2, 1, 5, 3 and 0):
=== ROCK PAPER SCISSORS === 1. Rock 2. Paper 3. Scissors 0. Quit Round 1 Your choice: 2 You: Paper | Machine: Rock -> You win! Round 2 Your choice: 2 You: Paper | Machine: Paper -> Draw Round 3 Your choice: 1 You: Rock | Machine: Scissors -> You win! Round 4 Your choice: 5 Error: choose between 0 and 3 Your choice: 3 You: Scissors | Machine: Rock -> The machine wins Round 5 Your choice: 0 --- Final result --- Rounds played: 4 Wins: 2 | Losses: 1 | Draws: 1
Use while (playing) with a flag playing = 1 that becomes 0 when the player picks 0. The machine’s move comes from the round with (round - 1) % 3 + 1, which gives 1, 2, 3, 1, 2, 3… And only count the round when it has really been played.
Loops in C exercises — Intermediate Level
Exercise 3 — Prime numbers up to n
Ask for a number n between 2 and 1000 (validated) and write all the primes from 2 to n, the total and how many divisions you did in total. A number is prime if it can only be divided by 1 and itself.
Expected output (with 30):
Find primes up to (2-1000): 30 Primes up to 30: 2 3 5 7 11 13 17 19 23 29 Total primes found: 10 Divisions made: 45
Two nested loops. The outer one goes through the candidates x from 2 to n; the inner one tries divisors i. Start each candidate with is_prime = 1 and, as soon as you find a divisor (x % i == 0), set it to 0 and break. There is no need to test up to x - 1: i * i <= x is enough (if x has a divisor larger than its square root, it also has a smaller one). With that you get exactly 45 divisions.
Exercise 4 — Login with 3 attempts in a single block
In the conditionals in C exercises we wrote a login with three attempts by writing the same block three times, because we did not have loops yet. Write it now with a single block that repeats: ask for the user’s initial (s) and the PIN (5678), three times at most, and finish as soon as the login is correct. If the three attempts are used up, the account is locked.
Expected output when it is right on the third attempt (with x 1234, s 1111 and s 5678):
=== LOGIN SYSTEM === Attempt 1 of 3 User initial: x PIN: 1234 Wrong user or PIN Attempt 2 of 3 User initial: s PIN: 1111 Wrong user or PIN Attempt 3 of 3 User initial: s PIN: 5678 Access granted. Welcome, sergio.
And when all three fail (with s 1, s 2 and a 1234):
=== LOGIN SYSTEM === Attempt 1 of 3 User initial: s PIN: 1 Wrong user or PIN Attempt 2 of 3 User initial: s PIN: 2 Wrong user or PIN Attempt 3 of 3 User initial: a PIN: 1234 Wrong user or PIN Account locked: 3 failed attempts
It is the same scheme as the guessing game in the practice: for (attempt = 1; attempt <= 3 && !logged_in; attempt++). To read the initial, scanf(" %c", &initial) with a space before %c: that way it skips the Enter left by the previous scanf.
Loops in C exercises — Final Challenge
Exercise 5 — Full interactive calculator
Put together everything from this block: loops, conditionals and types. The calculator:
- Shows a menu (add, subtract, multiply, divide, modulo, power and quit) and validates the option.
- Asks for two numbers; the second cannot be 0 when dividing or in the modulo (it is asked again).
- Calculates the power with a
for, withoutpow. - Asks whether you want another operation.
- On quitting, shows how many operations were done and the largest result.
Expected output (15 + 7, 10 / 0 corrected to 3, 2 ^ 10 and quit):
=== INTERACTIVE CALCULATOR === 1. Add 2. Subtract 3. Multiply 4. Divide 5. Modulo 6. Power 0. Quit Operation: 1 Number A: 15 Number B: 7 15.00 + 7.00 = 22.00 Another operation? (y/n): y 1. Add 2. Subtract 3. Multiply 4. Divide 5. Modulo 6. Power 0. Quit Operation: 4 Number A: 10 Number B: 0 Error: you cannot divide by zero Number B: 3 10.00 / 3.00 = 3.3333 Another operation? (y/n): y 1. Add 2. Subtract 3. Multiply 4. Divide 5. Modulo 6. Power 0. Quit Operation: 6 Number A: 2 Number B: 10 2.00 ^ 10 = 1024.00 Another operation? (y/n): n --- Summary --- Operations done: 3 Largest result: 1024.00
The main loop is a do…while (again == 'y' || again == 'Y'), because at least one operation is always done (or quitting is chosen, with a break). Inside, two more do…while loops validate the option and the divisor. For the largest, the first result always wins: if (operations == 1 || result > largest).
What’s wrong here?
Three snippets that compile. Before reading the explanation, predict what they print.
Snippet 1: the primes that do not show up
#include <stdio.h>
int main(void) {
int x, i, is_prime, total = 0;
printf("Primes up to 30:\n");
for (x = 2; x <= 30; x++) {
is_prime = 1;
for (i = 2; i < x; i++);
{
if (x % i == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
printf("%d ", x);
total++;
}
}
printf("\nTotal: %d (x = %d)\n", total, x);
return 0;
}
What does it print? Think about it before reading on.
Primes up to 30: Total: 0 (x = 2)
Not a single prime, and the outer loop stopped at x = 2. The ; at the end of for (i = 2; i < x; i++); leaves that for with an empty body: it does its laps doing nothing and ends with i == x. The block of braces that follows no longer belongs to the inner for but to the outer one, and it runs once for each x. With x = 2, i is 2, 2 % 2 == 0 is true and is_prime becomes 0. And then the break, which you thought left the inner for, leaves the only loop it is in now: the outer one. That is why x stays at 2 and the total is 0.
gcc warned about it with -Wall, just like in the theory story:
primes_sc.c: In function 'main': primes_sc.c:9: warning: this 'for' clause does not guard... [-Wmisleading-indentation] primes_sc.c:10: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'
A ; after for (...) or while (...). It is not a compile error; at most, a -Wmisleading-indentation warning. Before looking for the bug in the logic, look at the end of every loop line.
The fix is to remove the ;. The correct version is the solution to exercise 3, which gives the 10 primes.
Always put the { on the same line as the for. With for (...) { there is no room for a stray ;.
Snippet 2: the converter that keeps on asking
With the input five (letters), what does this converter do? It has a 4-lap emergency brake so the output is not infinite:
#include <stdio.h>
int main(void) {
int number = -1;
int laps = 0;
do {
if (++laps > 4) { // emergency brake (only for the article)
printf("[emergency brake: 4 laps]\n");
return 1;
}
printf("Enter a number (1-10, 0 to quit): ");
scanf("%d", &number);
if (number < 0 || number > 10) {
printf("Error: enter a number between 0 and 10\n");
}
} while (number != 0);
return 0;
}
Enter a number (1-10, 0 to quit): five Error: enter a number between 0 and 10 Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10 Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10 Enter a number (1-10, 0 to quit): Error: enter a number between 0 and 10 [emergency brake: 4 laps]
It only read five once, but asked four times (and without the brake, forever). scanf("%d") cannot turn five into a number: it returns 0, does not touch number (which stays at -1) and leaves the letters in the input. On the next lap it finds five again, fails again, and the condition number != 0 is still true.
scanf inside a loop without checking what it returns. It works with numbers and becomes infinite with the first letter.
The fix is in the solution to exercise 1: if (scanf("%d", &number) != 1), empty the line with while ((c = getchar()) != '\n' && c != EOF); and set number = -1.
Snippet 3: the login that gives four attempts
With four failed attempts ready in the input, how many times does it ask?
#include <stdio.h>
int main(void) {
char initial;
int pin;
int attempt = 0;
int logged_in = 0;
while (attempt <= 3 && !logged_in) {
attempt++;
printf("Attempt %d of 3 - initial and PIN: ", attempt);
scanf(" %c %d", &initial, &pin);
if (initial == 's' && pin == 5678) {
logged_in = 1;
}
}
printf("%s\n", logged_in ? "Logged in" : "Locked");
return 0;
}
Attempt 1 of 3 - initial and PIN: x 1 Attempt 2 of 3 - initial and PIN: x 2 Attempt 3 of 3 - initial and PIN: x 3 Attempt 4 of 3 - initial and PIN: x 4 Locked
“Attempt 4 of 3”. The counter starts at 0 and goes up at the start of the body, so the condition is checked with 0, 1, 2 and 3: true four times with attempt <= 3. It is the extra lap from <= in the theory, disguised because attempt++ is not where you are used to seeing it.
Mixing a counter that starts at 0 with a condition designed to count from 1. Either start at 0 and use <, or start at 1 and use <=.
With attempt < 3 (and everything else the same) you get exactly three attempts:
Attempt 1 of 3 - initial and PIN: x 1 Attempt 2 of 3 - initial and PIN: x 2 Attempt 3 of 3 - initial and PIN: x 3 Locked
When the number of laps matters, write the loop as a for: start, condition and step sit on the same line and the mistake jumps out at you.
Commented solutions
Solution to exercise 1
#include <stdio.h>
int main(void) {
int number;
int c;
printf("=== NUMBER TO WORD CONVERTER ===\n");
do {
printf("Enter a number (1-10, 0 to quit): ");
if (scanf("%d", &number) != 1) {
while ((c = getchar()) != '\n' && c != EOF); // throws the word away
number = -1; // and carries on as a bad number
}
if (number == 0) {
printf("Goodbye\n");
} else if (number < 0 || number > 10) {
printf("Error: enter a number between 0 and 10\n\n");
} else {
printf("%d -> ", number);
switch (number) {
case 1: printf("one\n"); break;
case 2: printf("two\n"); break;
case 3: printf("three\n"); break;
case 4: printf("four\n"); break;
case 5: printf("five\n"); break;
case 6: printf("six\n"); break;
case 7: printf("seven\n"); break;
case 8: printf("eight\n"); break;
case 9: printf("nine\n"); break;
default: printf("ten\n"); break;
}
printf("\n");
}
} while (number != 0); // the 0 is handled (goodbye) and then it stops
return 0;
}
Why it is done this way:
do…whileand notwhile: you have to ask at least once. Withwhile (number != 0)you would have to invent a starting value fornumberjust to get in.- 0 is handled inside: the goodbye is printed in the same lap in which 0 arrives, and then the condition
number != 0stops it. If the condition were at the start (while), there would be no natural place for “Goodbye”. defaultin theswitch: by the time it reaches theswitch,numberis already between 1 and 10, sodefaultcan only be 10. Using it avoids acase 10and aswitchwith no route for anything else.- Letters: if
scanfreturns anything other than 1, the line is emptied andnumber = -1makes theelse ifgive the error. Without this, you get snippet 2.
Solution to exercise 2
#include <stdio.h>
int main(void) {
int player, machine;
int playing = 1; // flag: the loop goes on while it is 1
int round = 1;
int wins = 0, losses = 0, draws = 0;
int c;
printf("=== ROCK PAPER SCISSORS ===\n");
printf("1. Rock 2. Paper 3. Scissors 0. Quit\n");
while (playing) {
printf("\nRound %d\n", round);
// Valid move: at least once and until it is between 0 and 3
do {
printf("Your choice: ");
if (scanf("%d", &player) != 1) {
while ((c = getchar()) != '\n' && c != EOF);
player = -1;
}
if (player < 0 || player > 3) {
printf("Error: choose between 0 and 3\n");
}
} while (player < 0 || player > 3);
if (player == 0) {
playing = 0; // the while will see it when it goes back up
} else {
machine = (round - 1) % 3 + 1; // no rand(): rock, paper, scissors, rock...
printf("You: %s | Machine: %s\n",
player == 1 ? "Rock" : player == 2 ? "Paper" : "Scissors",
machine == 1 ? "Rock" : machine == 2 ? "Paper" : "Scissors");
if (player == machine) {
printf("-> Draw\n");
draws++;
} else if ((player == 1 && machine == 3) ||
(player == 2 && machine == 1) ||
(player == 3 && machine == 2)) {
printf("-> You win!\n");
wins++;
} else {
printf("-> The machine wins\n");
losses++;
}
round++; // only counts a round actually played
}
}
printf("\n--- Final result ---\n");
printf("Rounds played: %d\n", round - 1);
printf("Wins: %d | Losses: %d | Draws: %d\n", wins, losses, draws);
return 0;
}
Why it is done this way:
- The
playingflag: when the player picks 0, it is set to 0 and thewhileends when it goes back up. Nobreakis needed: the rest of the body is in theelse, so nothing else runs. round++only in theelse: round 5 (the 0) is not played, which is whyround - 1= 4 is printed at the end. Rejected rounds (the 5) do not raise the counter either, because thedo…whilestays on the same round until it has valid input.- The machine without
rand():(round - 1) % 3 + 1gives 1, 2, 3, 1… for rounds 1, 2, 3, 4. With that, round 4 is won by the machine (rock against scissors). - The three wins with
||: rock beats scissors (1 against 3), paper beats rock (2 against 1) and scissors beat paper (3 against 2). Whatever is neither a draw nor a win is a loss, and theelsecovers that.
| Round | Player | Machine | Result | W / L / D |
|---|---|---|---|---|
| 1 | 2 Paper | 1 Rock | You win | 1 / 0 / 0 |
| 2 | 2 Paper | 2 Paper | Draw | 1 / 0 / 1 |
| 3 | 1 Rock | 3 Scissors | You win | 2 / 0 / 1 |
| 4 | 3 Scissors (after rejecting a 5) | 1 Rock | The machine wins | 2 / 1 / 1 |
The three wins fit in a single calculation: (player - machine + 3) % 3 == 1. Rock against scissors: (1 − 3 + 3) % 3 = 1. It works, but in an exam the version with || can be understood without thinking.
Solution to exercise 3
#include <stdio.h>
int main(void) {
int n, x, i;
int is_prime;
int total = 0;
int divisions = 0; // how many divisions we have done in total
int c;
do {
printf("Find primes up to (2-1000): ");
if (scanf("%d", &n) != 1) {
while ((c = getchar()) != '\n' && c != EOF);
n = 0;
}
if (n < 2 || n > 1000) {
printf("Error: enter a number between 2 and 1000\n");
}
} while (n < 2 || n > 1000);
printf("\nPrimes up to %d:\n", n);
for (x = 2; x <= n; x++) { // each candidate
is_prime = 1; // innocent until proven otherwise
for (i = 2; i * i <= x; i++) { // testing up to the square root of x is enough
divisions++;
if (x % i == 0) {
is_prime = 0;
break; // there is a divisor: no need to go on
}
}
if (is_prime) {
printf("%d ", x);
total++;
}
}
printf("\n\nTotal primes found: %d\n", total);
printf("Divisions made: %d\n", divisions);
return 0;
}
Why it is done this way:
is_prime = 1inside the outerfor: each candidate starts out “innocent”. If you set it only once before the loops, as soon as 4 set it to 0 no later number could ever be prime again.breakat the first divisor: if 30 can be divided by 2, it is not prime; testing 3 and 5 is a waste of time. And thisbreakonly leaves the innerfor, which is what we want.i * i <= x: ifx = a · b, one of the two factors is at most the square root ofx. For 29 it is enough to test 2, 3, 4 and 5 (6 · 6 = 36 > 29). Withi < xyou would test up to 28.- The divisions counter: it is there to check the improvement. With
i * i <= xyou get 45 divisions forn = 30; withi < x, 135.
How those 45 divisions are spread out is shown in the grid: composites fall at their first divisor (nearly all of them at 2) and only primes go all the way to their square root.
x | Divisors tested (i * i <= x) | Result |
|---|---|---|
| 2, 3 | none (2 · 2 > 3) | prime, 0 divisions |
| 9 | 2, 3 | 9 % 3 == 0: not prime |
| 25 | 2, 3, 4, 5 | 25 % 5 == 0: not prime |
| 29 | 2, 3, 4, 5 | prime (6 · 6 = 36 > 29) |
Solution to exercise 4
#include <stdio.h>
#define PIN_OK 5678
int main(void) {
char initial;
int pin;
int attempt;
int logged_in = 0; // flag: 1 when the login is correct
int c;
printf("=== LOGIN SYSTEM ===\n");
// ONE single block that repeats: 3 times at most, fewer if they get in
for (attempt = 1; attempt <= 3 && !logged_in; attempt++) {
printf("\nAttempt %d of 3\n", attempt);
printf("User initial: ");
scanf(" %c", &initial); // the space skips the previous Enter
printf("PIN: ");
if (scanf("%d", &pin) != 1) {
while ((c = getchar()) != '\n' && c != EOF);
pin = -1; // a PIN that is never right
}
if (initial == 's' && pin == PIN_OK) {
logged_in = 1;
} else {
printf("Wrong user or PIN\n");
}
}
if (logged_in) {
printf("\nAccess granted. Welcome, sergio.\n");
} else {
printf("\nAccount locked: 3 failed attempts\n");
}
return 0;
}
Why it is done this way:
- A single block: the body of the
foris what was copied three times in conditionals. If tomorrow the login allows 5 attempts, you change one number. - Two ways out in the condition:
attempt <= 3 && !logged_in. If the login is right on the first attempt,logged_inbecomes 1 and thefordoes not ask again. - The final message depends on the flag, not the counter: on leaving after getting it right on the third,
attemptis 4, the same as on leaving after failing all three. Onlylogged_intells the two cases apart. scanf(" %c")with a space: without it, the second lap would read the Enter left by the previous PIN as the initial and the login would fail without the user being able to type anything.pin = -1ifscanffails: if someone types letters in the PIN, they are thrown away and the PIN is one that is never right, instead of keeping the previous attempt’s.
Solution to exercise 5
#include <stdio.h>
int main(void) {
int option;
float a, b, result = 0, largest = 0;
char again = 'n';
int operations = 0;
int exp, j;
int c;
printf("=== INTERACTIVE CALCULATOR ===\n");
do {
printf("\n1. Add 2. Subtract\n");
printf("3. Multiply 4. Divide\n");
printf("5. Modulo 6. Power\n");
printf("0. Quit\n");
do { // valid option
printf("Operation: ");
if (scanf("%d", &option) != 1) {
while ((c = getchar()) != '\n' && c != EOF);
option = -1;
}
if (option < 0 || option > 6) {
printf("Error: choose between 0 and 6\n");
}
} while (option < 0 || option > 6);
if (option == 0) {
break; // leaves the main do...while
}
printf("Number A: ");
scanf("%f", &a);
do { // B cannot be 0 when dividing
printf("Number B: ");
scanf("%f", &b);
if ((option == 4 || option == 5) && b == 0) {
printf("Error: you cannot divide by zero\n");
}
} while ((option == 4 || option == 5) && b == 0);
switch (option) {
case 1: result = a + b; printf("%.2f + %.2f = %.2f\n", a, b, result); break;
case 2: result = a - b; printf("%.2f - %.2f = %.2f\n", a, b, result); break;
case 3: result = a * b; printf("%.2f * %.2f = %.2f\n", a, b, result); break;
case 4: result = a / b; printf("%.2f / %.2f = %.4f\n", a, b, result); break;
case 5:
result = (int)a % (int)b; // % only works with integers
printf("%d %% %d = %.0f\n", (int)a, (int)b, result);
break;
default:
exp = (int)b;
result = 1; // product accumulator: starts at 1
for (j = 0; j < exp; j++) {
result *= a;
}
printf("%.2f ^ %d = %.2f\n", a, exp, result);
break;
}
operations++;
if (operations == 1 || result > largest) {
largest = result; // the first one is always the largest so far
}
printf("Another operation? (y/n): ");
scanf(" %c", &again);
} while (again == 'y' || again == 'Y');
printf("\n--- Summary ---\n");
printf("Operations done: %d\n", operations);
if (operations > 0) {
printf("Largest result: %.2f\n", largest);
}
return 0;
}
Why it is done this way:
- Main
do…while: the menu is shown at least once. Option 0 leaves withbreak, because after choosing it there is no need to ask for numbers or “Another operation?”. - The divisor is validated with its own
do…while: the condition(option == 4 || option == 5) && b == 0is only true when it has to repeat; when adding, a 0 is a normal number. - The power with a
for:resultstarts at 1 (the neutral element of multiplication) and is multiplied byaexptimes. With 2 and 10: 2¹⁰ = 1024. - The largest: the first operation always is (
operations == 1); after that, only if it beats it. If it started withlargest = 0and every result were negative, the summary would say 0, which never came out. (int)a % (int)bin the modulo:%does not work withfloat, which is why they are turned into integers.
| Lap | Operation | result | operations | largest |
|---|---|---|---|---|
| 1 | 15 + 7 | 22.00 | 1 | 22.00 (the first) |
| 2 | 10 / 3 (after rejecting 0) | 3.3333 | 2 | 22.00 |
| 3 | 2 ^ 10 | 1024.00 | 3 | 1024.00 |
Watch it step by step in Python Tutor
Python Tutor (pythontutor.com) runs the code line by line. Choose C and paste this cut-down version of snippet 1, without the break, which checks the numbers from 2 to 7:
#include <stdio.h>
int main(void) {
int x, i, is_prime;
for (x = 2; x <= 7; x++) {
is_prime = 1;
for (i = 2; i < x; i++);
{
if (x % i == 0) {
is_prime = 0;
}
}
printf("%d -> %d\n", x, is_prime);
}
return 0;
}
Output:
2 -> 0 3 -> 0 4 -> 0 5 -> 0 6 -> 0 7 -> 0
What to watch step by step:
- With
x = 2, the arrow goes through the innerforline and jumps straight to the block of braces: the body of theforis the;. - With
x = 5, the innerfordoes three laps (i = 2, 3, 4) without running anything, and reaches the block withi = 5. - In the block,
x % iis alwaysx % x, which is 0: that is why every number comes out withis_prime = 0, even 5 and 7. - Remove the
;and run it again: now 2, 3, 5 and 7 come out with 1.
Cheat sheet: loops in C
The essentials in 30 seconds: for when you know how many laps, while when you do not, do…while when it has to be done at least once (validating). The condition is checked before every lap (after, in do…while), and on exit the variable is the first value that made it false. break leaves the innermost loop; continue jumps to the next lap. Every scanf inside a loop gets checked.
| I want to… | Code | Result |
|---|---|---|
| Repeat n times | for (i = 0; i < n; i++) | n laps, i ends at n |
| Count from 1 to n, both included | for (i = 1; i <= n; i++) | n laps, i ends at n + 1 |
| Count backwards | for (i = n; i >= 1; i--) | n, n − 1, …, 1 |
| Go in steps of 2 | for (i = 0; i < n; i += 2) | 0, 2, 4… |
| Two variables at once | for (i = 0, j = n; i < j; i++, j--) | They cross in the middle |
| Repeat until a sentinel | while (scanf("%d", &x) == 1 && x != 0) | Stops at 0 or at letters |
| Ask for a value until it is valid | do { … } while (bad value); | At least once |
| Loop with the way out inside | while (1) { … if (end) break; } | Leaves through the break |
| Skip a lap | if (…) continue; | In a for, carries on with i++ |
| Leave two loops | A flag in both conditions | for (…; … && !done; …) |
Empty the input after a failed scanf | while ((c = getchar()) != '\n' && c != EOF); | Throws away what was typed up to the Enter |
Templates from the exercises:
// Validate a number (exercises 1, 2, 3 and 5)
do {
printf("Value: ");
if (scanf("%d", &value) != 1) {
while ((c = getchar()) != '\n' && c != EOF);
value = -1; // a value that falls into the error
}
} while (value < MIN || value > MAX);
// Search with break (exercise 3)
found = 0;
for (i = 2; i * i <= x; i++) {
if (x % i == 0) {
found = 1;
break; // only leaves this for
}
}
// Several attempts with a flag (exercise 4)
for (attempt = 1; attempt <= MAX && !ok; attempt++) {
/* ask and check; if it is right: ok = 1; */
}
if (ok) { /* success */ } else { /* locked */ }
| Symptom | Likely cause |
|---|---|
| The body runs only once with an odd value | ; after for (...) |
| The program hangs without printing anything | ; after while (...) |
| A question repeats without waiting for you to type | scanf failed with letters and the input was not emptied |
| One lap too many (“Attempt 4 of 3”) | <= with a counter that starts at 0 |
break does not stop the outer loop | break only leaves the innermost one: use a flag |
continue leaves the while stuck | The step is after the continue |
| Garbage result in an accumulator | The starting value (0 or 1) before the loop is missing |
| Negative or absurd numbers in a factorial | int overflow: use long long |
Before handing in, ask yourself:
- How many laps does each loop do with the data in the task? (Count them with the
<and<=rule.) - What is the control variable worth on exit, and do I use it afterwards?
- Is there any
;right after afor (...)or awhile (...)? - What happens if the user types letters at every
scanf?
That wraps up loops. The next topic is pointers in C, where a loop will walk through memory for real and an extra lap no longer prints a stop that does not exist, but reads memory that is not yours. And once you have the whole IC2 practical syllabus, test yourself with the IC2 practical mock exam.
Keep goingAll C articlesDid this article help you? Here is how you can support SergioLearns →




What is an operating system — theory with your laptop on a study afternoon, a write() call and a CPU that never rests (and the three programs that weren’t running at the same time)