Computers are fast. Stupidly fast. The one thing they do better than anything else is the same tiny task, over and over, without getting bored or losing focus. A game that updates the screen 60 times a second is doing exactly that — running the same handful of instructions 60 times a second, every second, for as long as you play. Without loops, we'd have to write those instructions out by hand 60 times. And then 60 more times for the next second. You can see where this is going.
This chapter is about giving our code the power to repeat itself. We'll meet the four main kinds of loop in C++, learn when to reach for each one, and pick up a few tricks for making loops fast and safe. By the end, you'll understand why every game you've ever played is, at its heart, a loop.
In this chapter, we will:
- Revisit the game loop with fresh eyes
- Write
whileloops for repeating while a condition holds - Use
do-whileloops when we need to run at least once - Use
forloops for counting and iterating - Meet the modern range-based
forloop - Control loops with
breakandcontinue - Think about loop performance and how to keep loops lean
- Debug loops — and spot the two classic mistakes
- Try an optional AI exercise
Let's start by remembering where we've already seen a loop in action.
Revisiting the Game Loop
Way back in Chapter 1, the "Controllable Square" project had a section of code that kept running until the player closed the window. At the time, we called it the render loop and moved on. Now you know what variables, conditions, and comparisons are, and the loop isn't mysterious anymore — it's just a block of code that repeats.
That's really the whole idea of a game. You do this, forever:
- Check what the player is doing (input)
- Update the state of the world (physics, AI, scoring)
- Draw the new state on the screen (rendering)
- Go back to step 1
A loop is what holds all of that together. Without it, the player would press a key once, the game would draw a single frame, and then the program would end. Every game engine on the planet, from SDL to Unreal, is ultimately wrapped around a loop just like this one.
while Loops
The while loop is the simplest loop in C++, and the one most closely tied to how we naturally describe repetition. "While something is true, keep doing this." That's literally the syntax:
while (condition) {
// This code runs over and over, as long as condition is true
}
Before each pass through the loop, C++ checks the condition. If it's true, the block inside the braces runs. When the block finishes, we're back at the top and the condition is checked again. As soon as the condition becomes false, the loop ends.
Here's a tiny countdown:
int countdown = 5;
while (countdown > 0) {
std::cout << countdown << "..." << std::endl;
countdown = countdown - 1;
}
std::cout << "Go!" << std::endl;
In the preceding code, we start with countdown at 5. The condition countdown > 0 is true, so we print the number and subtract one. This keeps going until countdown reaches 0, at which point the condition is false and the loop stops. The output is 5..., 4..., 3..., 2..., 1..., Go!.
Notice the line countdown = countdown - 1;. That's what makes this loop end. Without it, countdown would stay at 5 forever, the condition would always be true, and the loop would run until you force-quit the program. This is called an infinite loop, and accidentally writing one is a rite of passage for every programmer.
The Intentional Infinite Loop
Games are infinite loops. Not because of a bug — because the game should keep running until the player tells it to stop. The cleanest way to write an intentional infinite loop is like this:
while (true) {
// Handle input
// Update world
// Draw frame
// If the player quit, break out (we'll meet break shortly)
}
The condition true is always true, so the loop never exits on its own. We rely on some piece of code inside the loop to break out when the player is ready to stop. The rule for a normal while loop is simple: something inside the loop must eventually make the condition false. If it doesn't, and you didn't mean to write an infinite loop, you've written a bug.
do-while Loops
The do-while loop is a close cousin of while, with one important difference: the condition is checked after the loop body runs, not before. That means the body is guaranteed to execute at least once, even if the condition is false from the start.
The syntax:
do {
// This code runs at least once, then repeats while condition is true
} while (condition);
Pay attention to two small details. The word while comes at the bottom this time, and there's a semicolon after the closing parenthesis. Forgetting that semicolon is an easy mistake.
Here's a classic use case — asking the player for a valid menu choice:
int choice = 0;
do {
std::cout << "Enter a choice (1-3): ";
std::cin >> choice;
} while (choice < 1 || choice > 3);
std::cout << "You chose " << choice << std::endl;
In the preceding code, we prompt the player for a number and read it into choice. Then we check: if the number is less than 1 or greater than 3, we loop back and ask again. We only leave the loop when the player enters a valid choice. do-while is the natural fit whenever the logic is "do this, then decide whether to do it again."
In practice, you'll reach for while far more often than do-while. But when do-while is the right tool, it's the right tool — don't force a while into a job it doesn't suit.
for Loops
The for loop is the workhorse of counting. Any time you want to do something a specific number of times — move 10 enemies, draw 100 stars, check every row on a tilemap — for is almost always the cleanest choice.
The syntax looks intimidating at first, but it's really just three small pieces packed into one line:
for (initialization; condition; update) {
// Loop body
}
- Initialization runs once, before the loop starts. Usually we declare a counter variable here.
- Condition is checked before each pass. If it's false, the loop ends.
- Update runs at the end of each pass. Usually it changes the counter.
Here's the countdown from earlier, rewritten as a for loop:
for (int countdown = 5; countdown > 0; countdown = countdown - 1) {
std::cout << countdown << "..." << std::endl;
}
std::cout << "Go!" << std::endl;
The three pieces of the for loop do the same work the while version did, but they're all in one place. All the moving parts are in one line of scaffolding — immediately clear to any reader.
Counting Up
The same structure works just as well counting up:
for (int i = 0; i < 10; i++) {
std::cout << "Enemy " << i << " spawned." << std::endl;
}
In the preceding code, we start i at 0, keep going while i < 10, and add one each pass with i++. That gives us values 0 through 9 — ten iterations in total. Starting at 0 is a C++ convention, and it'll feel natural very soon.
Loop Variable Scope
A counter variable declared inside the for itself only exists inside the loop. Once the loop ends, the variable is gone. Try to use it afterwards and the compiler will stop you — a good thing, because it keeps temporary counters from cluttering up the rest of your code.
for (int i = 0; i < 5; i++) {
std::cout << i << std::endl;
}
// std::cout << i << std::endl; // This would be a compiler error!
Nested for Loops
You can put one for loop inside another. This is exactly the right tool for anything grid-shaped — rows and columns, a chessboard, a tilemap.
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 4; col++) {
std::cout << "Tile at (" << col << ", " << row << ")" << std::endl;
}
}
In the preceding code, the outer loop goes through rows 0, 1, and 2. For each of those rows, the inner loop goes through columns 0, 1, 2, and 3 — giving us 3 × 4 = 12 tiles in total. Descriptive names like row and col make the loop far easier to read than bare i and j. Future you will thank present you.
Range-based for Loops
Often we want to loop over every item in a collection — every enemy in a list, every high score in an array, every letter in a string. C++ has a special form of for loop for exactly this, called the range-based for loop.
We haven't formally met collections yet — those are coming up in Act 2 — but the syntax is one of the friendliest loops in C++. Here it is on a std::string:
std::string playerName = "Ada";
for (char letter : playerName) {
std::cout << letter << std::endl;
}
In the preceding code, we ask C++ to give us each char in playerName, one at a time, calling it letter inside the loop. The output is A, d, a, each on its own line. No indexes, no starting points, no off-by-one errors. The structure is:
for (type variable : collection) {
// Use variable
}
Here's a slightly more useful example. Let's count how many vowels are in a player's name:
std::string playerName = "Ada Lovelace";
int vowelCount = 0;
for (char letter : playerName) {
if (letter == 'a' || letter == 'e' || letter == 'i' ||
letter == 'o' || letter == 'u') {
vowelCount++;
}
}
std::cout << "Vowels: " << vowelCount << std::endl;
We visit every character one at a time. For each letter, we check whether it's one of the five lowercase vowels. If it is, we add one to vowelCount. Notice how much less scaffolding there is compared to a regular for loop — no counter, no condition, no update. We'll see this loop used heavily once we start working with arrays and vectors in Act 2.
break and continue
Sometimes you want to escape a loop early, or skip the rest of the current pass and jump to the next one. C++ gives us two keywords for exactly this: break and continue.
break
break immediately exits the loop it's in. As soon as C++ sees break, it jumps straight to the code after the loop.
int lives = 3;
while (true) {
lives = lives - 1;
std::cout << "Lives remaining: " << lives << std::endl;
if (lives <= 0) {
std::cout << "Game over!" << std::endl;
break;
}
}
std::cout << "Back to the main menu." << std::endl;
In the preceding code, we have a while (true) — an intentional infinite loop. Each pass, we subtract a life. When lives reaches 0, we print the game over message and break out. Without that break, the loop would never end. break is how we quit a game loop: the condition is true always, but somewhere inside, we check whether the player wants to quit. If they do, break.
continue
continue is break's quieter cousin. Instead of exiting the loop entirely, it skips the rest of the current pass and jumps back to the top for the next iteration.
for (int enemyID = 0; enemyID < 10; enemyID++) {
if (enemyID == 5) {
continue; // Skip enemy 5 — maybe it's already dead
}
std::cout << "Updating enemy " << enemyID << std::endl;
}
In the preceding code, we loop through enemy IDs 0 to 9. When we hit enemy 5, we continue — C++ jumps straight back to the top and carries on. The "Updating enemy 5" line is never printed. continue is handy when you want to skip certain elements but not exit the loop — a common game use is "update every entity except the ones that are already dead."
A small piece of advice: break and continue are useful, but overusing them makes loops hard to follow. If a loop has four continues and two breaks scattered through it, consider whether the logic would be clearer as a clean condition on the loop itself.
Loop Performance
Most of the time, loops are fast enough that you don't need to think about performance. But games are one of the places where loops run a lot — 60 times a second, over thousands of entities, for however long the player plays.
Don't Repeat Work Inside the Loop
The biggest performance mistake beginners make is computing the same value over and over inside a loop when it could be computed once outside.
// Bad — we calculate the bonus on every single pass
for (int i = 0; i < 1000; i++) {
int bonus = 50 * 3 + 25;
std::cout << "Score: " << (i + bonus) << std::endl;
}
The value of 50 * 3 + 25 is 175. Every time. It doesn't change. But above, C++ does that math on every pass — a thousand times, when it only needed to happen once. Pull it out:
// Good — the bonus is calculated once, outside the loop
int bonus = 50 * 3 + 25;
for (int i = 0; i < 1000; i++) {
std::cout << "Score: " << (i + bonus) << std::endl;
}
The rule is simple: if a value inside your loop doesn't change between passes, compute it before the loop and use the result inside. The same principle applies to the loop condition itself — if the condition calls a function that always returns the same number, store the result in a variable first.
Nested Loops Multiply
If an outer loop runs 100 times and the inner loop runs 100 times, the inner block runs 100 × 100 = 10,000 times. Triple-nested? A million. This matters because collision detection is the classic example: for each of your 50 enemies, check against each of your 50 bullets. That's 2,500 checks per frame, and at 60 frames per second, 150,000 checks per second. The intuition to remember is that nested loops multiply, they don't add. Most of the time the numbers are small and cheap, and you move on. Occasionally the answer is huge and expensive, and you need a smarter approach.
Debugging Loops
Two loop bugs will bite every beginner at some point, so it's worth meeting them before they meet you.
The first is the infinite loop. Your program runs, your game window doesn't appear (or freezes), and nothing responds. The cause is almost always a loop condition that never becomes false. In Visual Studio, stop the program with Shift+F5. Then set a breakpoint inside the loop and run it again. Step through one iteration at a time with F10 and watch the variable your loop condition depends on. If it isn't changing the way you expected — or at all — you've found your bug.
The second is the off-by-one error. Your loop runs, but it runs one too many times or one too few. Maybe you wrote i <= 10 when you meant i < 10, and now you've got 11 enemies instead of 10. The debugger is your friend here too — set a breakpoint at the top of the loop and watch the counter.
These two bugs together probably account for more debugging sessions in a beginner's first year than any other cause. Knowing what they look like means you'll recognize them the moment they appear.
AI Exercise (Optional)
Loops are a great topic to explore with an AI, because the same problem can usually be solved by several different loops, and comparing them is genuinely educational.
Open your AI chatbot of choice. Paste in this prompt:
"I'm a C++ beginner. I've just learned about while, do-while, for, and range-based for loops. Please write four small, separate snippets — one per loop type — that each solve the same simple task: printing the squares of the numbers 1 to 5 (1, 4, 9, 16, 25). Include a brief comment before each snippet explaining why that loop type suits the task, and at the end give me a one-paragraph summary of which loop you'd actually reach for first in real code, and why."
Notice what the preceding prompt is doing. It pins down exactly what you've just learned, asks for a specific and concrete task, and forces the AI to justify its choices. The "which would you reach for first" question is the trick — it turns the AI from a syntax-producer into something closer to a mentor.
Read the response carefully. Do all four loops actually produce the same output? Can you follow each one? And does the AI's final recommendation match the one you'd make? If it doesn't, ask it why. "I would have used a for loop here. Why did you prefer range-based for?" is a perfectly good follow-up, and the answer will usually teach you something.
Summary
Loops are the engine of almost every non-trivial program, and especially every game. You now know the four main kinds of loop in C++ — while, do-while, for, and range-based for — and when each one is the natural choice. You can escape a loop early with break, skip a single pass with continue, and you've seen why nested loops multiply rather than add. You also know how to spot the two classic loop bugs when they show up, because they absolutely will.
The real power of loops comes when we stop doing the same thing over and over and start doing useful work that we can package up and reuse. That's what functions are for, and they're what the next theory chapter covers. Before that, we'll put loops to work in a real SDL project in the next chapter.