CS U111 · Lab 4 · Lesson — first time through

Loops, from the beginning

One idea at a time, with the reasoning before the syntax. About 40 minutes if you stop at each "check yourself" and actually try it. The compressed version lives on the notes page; every problem from the sheet is worked out on the solutions page.

Start here — and when to read this

If a lab evaluation is tomorrow and your evening is short, this is not the page to read tonight — go to the solutions page, do its 45-minute path, and come back here afterwards. This page is for the calm hour when you want loops to actually make sense rather than to be survived.

What's genuinely true about where you stand: everything in Lab 4 started from zero four weeks ago, for the whole batch. No school syllabus in India teaches while, do-while and for in C. Classmates who look fluent have written a few more programs than you have, and that is the only difference — it is made of hours, and hours are available. The material itself leans on exactly two pieces of school arithmetic: remainder (%) and division that throws away the fraction (/).

1 · The problem loops exist to solve

Print "Hello" five times, with what you have from Labs 1–3, and you write printf five times. It works. Now print it ten thousand times. Now print it a number of times the user chooses — a number you don't know when you're writing the program, because it doesn't exist yet.

That last one is the real wall. You cannot copy and paste a line a number of times that isn't known yet. What you need is a way to say something you have never said to a computer before:

The shift this lab asks for

Labs 1–3 answered "which path should the program take?" — that's if. Lab 4 answers "how many times should this happen, and when should it stop?" Everything else on the sheet is a consequence of that one new question.

Here is the mechanism, and it is smaller than you'd expect. A loop is a condition and a block of code, wired together in a cycle:

  1. Check the condition. Is it true?
  2. If yes: run the block, then go back to step 1.
  3. If no: skip the block and carry on with the rest of the program.

That's it. Everything in this lab — five kinds of loop syntax, nested loops, break, patterns, prime tests — is that cycle with different things filled in. Notice what has to be true for the cycle ever to end: something inside the block must change something the condition looks at. If not, the answer at step 1 never changes, and the program runs until you kill it. That single sentence explains half the bugs in this lab.

2 · Counting, and the ++ that does it

Loops count constantly, so C gives you three ways to write "add one to count", each shorter than the last:

count = count + 1;   // the long way — nothing wrong with it
count += 1;          // compound assignment
count++;             // the increment operator

All three do exactly the same thing when they sit alone on a line. -- is the same story for subtracting one. And += generalises: sum += d means sum = sum + d, which is the shape of every accumulator you will write.

There is one wrinkle the lab sheet raises, and it matters only in a specific situation. You can write count++ (postfix) or ++count (prefix). The difference appears only when the increment is inside a bigger expression:

int a = 5;
int b = a++;   // POSTfix: b takes the OLD value (5), then a becomes 6
// a = 6, b = 5

int x = 5;
int y = ++x;   // PREfix: x becomes 6 FIRST, then y takes the new value
// x = 6, y = 6
a=6, b=5
x=6, y=6

Remember it by reading the position of the ++ as when it happens: after the variable means afterwards, before the variable means beforehand. In a loop header like for (int i = 0; i < 5; i++) the update stands on its own, so i++ and ++i behave identically — use whichever, and don't lose time over it.

Classic trap · ++ inside a bigger expression

Code like arr[i++] = i; or printf("%d %d", i++, i++) is genuinely ambiguous in C, and examiners occasionally ask you to say so. The professional habit is simply to never write it: put the increment on its own line, where it means one thing.

3 · The while loop, built slowly

The simplest loop is the cycle written literally. "Keep going while this is true":

while (condition) {
    // statements to repeat
}

Print "Hello" five times. Build it from the three questions rather than copying it:

  1. What is the state? We need to know how many we've printed. Call it count, starting at 0 — nothing printed yet.
  2. What keeps it going? count < 5. Fewer than five printed so far, so print another.
  3. What advances? count++ after each print — otherwise count stays 0 forever and so does the answer to question 2.
int count = 0;             // 1. initialisation

while (count < 5) {        // 2. condition
    printf("Hello\n");
    count++;               // 3. update
}

Now the part that separates people who can write loops from people who can only recognise them: trace it. On paper, one row per pass, one column per variable. Never hold two numbers in your head at once.

passcount at the testcount < 5?printscount after
10trueHello1
21trueHello2
32trueHello3
43trueHello4
54trueHello5
—5falsenothingloop ends

Five Hellos, and count ends at 5, not 4 — because the last thing the loop does is fail the test, and it has to reach 5 to fail it. That "one past the end" feeling is normal and it is where off-by-one errors are born. Tracing is how you catch them; there is no substitute, and every good programmer still does it.

Check yourself: change the condition to count <= 5. How many Hellos?

Six. count takes the values 0, 1, 2, 3, 4, 5 — that's six passes — and only fails at 6. This is the entire off-by-one error, and it is Debugging Exercise 1 on the lab sheet. When it matters, count the values the variable actually takes; don't reason about it in words.

A loop that runs zero times

A while tests before the body, so if the condition is false at the start, the body never runs at all:

int count = 10;
while (count < 5) {      // false immediately
    printf("Hello\n");   // never happens
    count++;
}

This is not an error. It is a feature you will rely on — and it is the boundary case §19 of the sheet keeps pointing at. The digit-extraction loop while (temp != 0) runs zero times when the input is 0, which is why a naive digit counter says the number 0 has zero digits. Examiners like that question precisely because it looks like a trick and isn't: it's just the definition of while being applied honestly.

4 · do-while, for when it must happen at least once

Sometimes you can't test first, because the thing you'd test doesn't exist yet. Asking the user for a positive number: you cannot check whether their answer is positive before they have answered. So the test moves to the bottom:

int choice;
do {
    printf("Enter a positive number: ");
    scanf("%d", &choice);
} while (choice <= 0);   // note the semicolon

Read it as "do this … while that stays true". The prompt always appears at least once; if they type −4, the condition is true and round we go again. That is the whole difference between while and do-while, and it is worth being able to say in one sentence, because it is asked in one sentence:

The one-line difference

while tests before the body, so it may run zero times. do-while tests after, so it always runs at least once. Dry Run 2 on the sheet is exactly this: while (0) runs zero times, do … while (0) runs once, so x goes from 5 to 6.

Use do-while for input validation, menus, and "keep asking until they get it right" — which is Problem 4, the PIN validator. Everywhere else, while or for reads better.

5 · The for loop, and exactly what it does when

Look back at the Hello loop and notice that its three parts were scattered: initialisation above the loop, condition in the header, update buried at the bottom of the body. In a long body, the update is easy to lose — and losing it is the infinite-loop bug. So C offers a shape that puts all three where you can see them at once:

for (initialisation; condition; update) {
    // body
}

for (int i = 0; i < 5; i++) {
    printf("Hello\n");
}

This is the same loop as the while version — same five Hellos, same values. Nothing new happens; the three parts just moved onto one line where a reader can check them in a glance. That's the only reason for exists.

What you do need to know exactly is the order things happen in, because most trace questions are really questions about this order:

Step through a for loop, one action at a time. The highlighted box is what C is doing right now. Watch where the update sits — after the body, not before it.

In words, and worth memorising as a list of five:

  1. Run the initialisation — once only, ever.
  2. Test the condition. False? Leave the loop immediately.
  3. Run the body.
  4. Run the update.
  5. Go back to step 2.

Two consequences fall straight out. The body sees i = 0 on the first pass (the update hasn't happened yet), and the loop variable ends up one past the last value the body saw. And a for loop, like a while, can run zero times — for (int i = 0; i < 0; i++) tests, fails, and does nothing.

Check yourself: how many times does for (int i = 1; i <= 10; i++) run the body, and what is i on the last pass?

Ten times; i is 10 on the last pass. General rule worth carrying: for (i = a; i <= b; i++) runs b − a + 1 times, and for (i = a; i < b; i++) runs b − a times. Deciding which you want before writing is faster than fixing it afterwards.

The three slots don't have to hold counting-by-one. Any expression works, and this is worth seeing once so it doesn't surprise you in a question:

for (int i = 10; i >= 1; i--)          // count down
for (int i = 2; i <= n; i += 2)        // step by two — even numbers only
for (int i = 5; i < 200 && keepGoing; i *= 2)   // double, with an extra condition
Classic trap · the semicolon that eats the body

Write for (int i = 1; i <= 3; i++); — note the ; before the brace — and the loop's body becomes that empty semicolon. It spins three times doing nothing, and the code you meant to repeat runs once, afterwards. Verified: the version that should print *** prints a single *. There is no error message; the program is legal C. When output makes no sense at all, check for this first.

Classic trap · indentation is not a body

Without braces, a loop repeats exactly one statement, however you indent the rest:

for (int i = 1; i <= 4; i++)
    printf("%d", i);
    printf("|");     // looks inside; is NOT inside

Verified output: 1234| — one bar, not four. C ignores your whitespace. Put braces on every loop body, even one-liners, and this bug can't reach you.

6 · Which loop should I use?

Any loop can be rewritten as any other, so this is a readability choice — but it is a marked one, and the right answer is usually obvious from how the problem is phrased:

UseWhenSignal words in the problem
forYou know the number of repetitions before starting"up to 10", "N terms", "for each row from 1 to n"
whileYou stop on a condition; the count is unknown"until the number becomes 0", "while there are digits left", "until they guess it"
do-whileThe body must run at least once"ask the user, then", "menu", "keep prompting until valid"

7 · Loops inside loops

Put a loop in another loop's body and something genuinely new becomes possible: two dimensions. The rule is one sentence — for every single pass of the outer loop, the inner loop runs all the way through — and the consequence is that the inner loop's counter restarts every time.

for (int i = 1; i <= 3; i++) {          // outer
    for (int j = 1; j <= 2; j++) {      // inner
        printf("i=%d, j=%d\n", i, j);
    }
}
i=1, j=1
i=1, j=2
i=2, j=1
i=2, j=2
i=3, j=1
i=3, j=2

Six lines from 3 × 2 — the inner loop's two passes happen inside each of the outer loop's three. The mental picture that makes patterns easy is a grid: the outer loop is the rows, the inner loop is the columns of one row, and the printf("\n") that ends a row belongs after the inner loop, inside the outer:

The right triangle of stars, as the grid the loops walk. Row i prints columns 1 … i, because the inner loop's limit is j <= i — the outer variable. Make it j <= n instead and the dashed cells fill too: a square.
int n = 5;
for (int i = 1; i <= n; i++) {          // rows
    for (int j = 1; j <= i; j++) {      // columns: as many as the row number
        printf("*");
    }
    printf("\n");                       // end this row
}
*
**
***
****
*****
Check yourself: move printf("\n") inside the inner loop. What comes out?

One star per line, fifteen lines. The newline would then run after every star instead of after every row. Where a statement sits relative to the braces is the logic — this is the single most common nested-loop mistake, and it is Debugging Exercise 4's cousin.

8 · Escaping early: break and continue

Two statements bend the cycle from inside the body.

break abandons the loop entirely. Not just this pass — the whole loop. Execution continues after the closing brace:

for (int i = 1; i <= 10; i++) {
    if (i == 5) break;
    printf("%d ", i);
}
// Output: 1 2 3 4

continue abandons only the current pass and jumps ahead to the update and the next test:

for (int i = 1; i <= 5; i++) {
    if (i == 3) continue;
    printf("%d ", i);
}
// Output: 1 2 4 5

Read both examples by walking the body top to bottom: on the pass where the if fires, the printf below it is never reached, which is why 5 and 3 are missing from their outputs.

Classic trap · continue in a while loop

In a for loop the update lives in the header, so continue still runs it and the loop keeps moving. In a while loop the update is just a statement in the body — and continue jumps straight over it to the condition. Nothing changes, and the program hangs. If you use continue inside a while, do the update before it.

Neither is required: a well-chosen condition often removes the need for both. But break is genuinely natural for "stop as soon as you find it" — which is why the sheet's prime check and Problem 5's guesser both use one.

9 · The three ways loops go wrong

Not a list to memorise — three stories, each with a symptom you can recognise in the exam hall.

The infinite loop: nothing advances

int x = 5;
while (x > 0) {
    printf("%d\n", x);
    // missing: x--;
}

Symptom: output pours out forever, or the program just sits there. Cause: the condition looks at x and nothing in the body changes x. Cure: Ctrl+C to kill it, then ask question 3 — how does this advance?

The off-by-one: the boundary is one out

for (int i = 1; i < 10; i++)   // wanted 1 to 10; stops at 9

Symptom: one line too few, or one too many. Cure: don't nudge the +1 until it looks right — count the values the variable actually takes. From 1 with i < 10: 1…9, nine passes. With i <= 10: 1…10, ten.

The uninitialised accumulator: adding to garbage

int sum;                       // contains whatever was in that memory
for (int i = 1; i <= 5; i++) sum = sum + i;

Symptom: a huge, arbitrary number — running this printed 221908503 on the machine these notes were written on, and it may differ on yours. Cause: C does not zero your variables; you get whatever bytes were lying there. Cure: int sum = 0; — and compile with -Wall, which warns you before you ever see the number.

The debugging habit worth having, from §18 of the sheet

When a loop misbehaves, do not guess and add +1 or -1 until it works. Go in order: (1) are all the accumulators initialised? (2) trace the first pass by hand — did it do what you expected? (3) write down the values just before it should stop — does the condition actually turn false there? (4) is the loop variable definitely changing, and in the right direction? (5) if still lost, put printf("DEBUG: i=%d, sum=%d\n", i, sum); inside the loop and look at what it prints. Step 5 feels like giving up; it is what professionals do first.

10 · Attacking a problem you haven't seen

This is the actual skill being examined, and it is a routine, not an inspiration. Take: "Read a positive integer and print how many digits it has."

  1. Write the required output on your paper first. One line: Digits: 3. Now you know what "finished" looks like.
  2. Ask the three questions.
    • State: the number as it shrinks (call it temp, starting at n), and a counter starting at 0.
    • Stop: when there is nothing left of the number — temp != 0 keeps it going.
    • Advance: remove one digit — temp /= 10.
  3. Choose the shape. The digit count is unknown before we start, so: while.
  4. Trace two passes on a small input before writing anything. For 452: temp 452 → 45 → 4 → 0, with digits 1 → 2 → 3. It works on paper, so it will work in C.
  5. Type it, compile early.
    int digits = 0, temp = n;
    while (temp != 0) {
        digits++;
        temp /= 10;
    }
    printf("Digits: %d\n", digits);
  6. Test the boundaries: zero iterations (input 0 → prints 0, arguably wrong — mention it), one iteration (input 7 → 1), typical (452 → 3).

Steps 1, 2 and 4 happen away from the keyboard, and they are where the marks are. Students who start typing at step 5 spend the session debugging; two minutes of tracing prevents twenty minutes of it.

Check yourself: adapt that routine to "print the sum of the digits" without looking at anything.

Same state plus an accumulator sum = 0; same stop; same advance. The only new line is grabbing the digit before you throw it away: sum += temp % 10; before temp /= 10;. That is the whole of Problem 1 — and once you see that "count them", "add them" and "reverse them" are three different lines inside one loop, you have understood what the problem was testing.

11 · Testing a loop properly

§19 of the lab sheet asks for representative test cases, and it is asking for four specific ones. Write them down before you write the loop:

CaseWhat it catchesExample
Zero timesConditions that are false at the start; accumulators printed without ever being filleddigit loop with input 0; "print n rows" with n = 0
Exactly onceOff-by-one at the small end; do-while versus while confusionsingle-digit input; n = 1
A typical runThe main logic — check it by hand, not by trusting the program452; n = 5
The maximumOff-by-one at the far end; the last row missing3 failed PIN attempts; the 10th row of a table

Volunteering these in a lab evaluation is worth marks even when the program isn't perfect, because designing tests is explicitly one of the learning outcomes on the sheet. Write them as a comment at the top of your file: /* tested: n=0 → 0 digits; n=7 → 1; n=452 → 3 */.

Asking an AI well — three prompts that help, pinned to this lab

Use it as a tutor, never as a ghostwriter — the handout examines your ability to verify generated code (lectures 40–41), and typing programs yourself is what builds the fluency the lab exam tests. Prompts that work:

  1. "Here is my C loop and the output I expected: [paste both]. Don't fix it — tell me which of the three loop parts (initialisation, condition, update) is wrong, and trace the first two passes so I can see it."
  2. "Give me four short C loops of the kind in Hanly & Koffman chapter 5, and ask me to predict the output of each one. Don't show the answers until I've replied. Include one that runs zero times."
  3. "I wrote this nested loop to print a pattern [paste]. Explain what my inner loop's condition makes each row do, in terms of rows and columns — no rewritten code."

Anything that begins "write a program that…" teaches you nothing and is against the point of the lab.

Where to go next

Before the sheet · the rung it skips

The lab sheet goes from syntax straight to problems that combine two or three ideas. Two pages fill the gap: the loops ladder (fourteen tiny programs in five rungs, each one idea, with outputs to check) and the predict-the-output drill (six generated loops per sheet, self-marking). Ladder first, then a drill sheet a day, then the sheet.

You now have the whole mechanism. The rest is repetition in the literal sense — writing loops until the three questions answer themselves.