As someone who looks at a lot of beginner code, I often see students use flags to terminate loops. While there is nothing inherently wrong with this practice, and sometimes it might even be necessary, I find that code is often easier to write and read using a different approach. Let’s talk about it!
Table of Contents
- What’s a Flag?
- What’s Wrong With Flags?
- Going a Bit Deeper: Loop Invariants
- Take My Advice With a Grain of Salt
What’s a Flag?
In code, a flag is a boolean variable (though, this is a fairly loose definition). That’s it! Here are some examples:
boolean isHurt = false; boolean onCooldown = true; boolean hasStrength = false;
In general, I can see why they’re enticing. After all, when I make a flag that has a descriptive name, it can make my code easier to read. And of course, booleans make a lot of sense: they have two states. For instance, from one of the examples above, I’m either hurt or I’m not.
That said, more often than not, I find flags to be troublesome in code, specifically loops. Let’s talk about it!
What’s Wrong With Flags?
There are a couple reasons why I don’t like flags. First, boolean variables in general don’t store a lot of information: only true or false. As a result, they don’t scale well as variables. For example, what if I had a game where I wanted to know if my player was hurt? We might have a boolean flag that tells us this. Though, I would argue that it’s better to make a method that tells us if the player is hurt based on their HP:
public boolean isHurt() { return this.hp < this.maxHP; }
Here, the information about being hurt comes from the HP, not a boolean that we have to update anytime HP related events occur (e.g., every time you are attacked, drink a potion, and take environment damage). That’s a lot of updates!
Of course, that’s my broad complaint about flags. The focus of the article today is on the use of flags in loops, specifically loop conditions. For example, we might have a loop that deals decaying damage over time to our player. The loop should only terminate if the player dies or the ailment decays to zero, so we might write something like this:
boolean isDead = false; boolean isCured = false; while (!isDead && !isCured) { this.hp -= this.maxHP * this.dot; this.dot -= .01; if (this.hp <= 0) { isDead = true; } if (this.dot <= 0) { isCured = true; } }
Now, experienced developers might say, “hey, wait a minute! You can simplify the if statements.” (Though, they might also point out the double comparison with equals…). As a result, you get something like this:
boolean isDead = false; boolean isCured = false; while (!isDead && !isCured) { this.hp -= this.maxHP * this.dot; this.dot -= .01; isDead = this.hp <= 0; isCured = this.dot <= 0; }
To be fair, this is a lot cleaner. However, I don’t love it because we have to dig into the loop to figure out how these conditions are computed. Instead, I would prefer something like this:
while (this.hp > 0 && this.dot > 0) { this.hp -= this.maxHP * this.dot; this.dot -= .01; }
And going back to what I said before, I would probably make these loop conditions into methods:
while (!this.isDead() && !this.hasDot()) { this.hp -= this.maxHP * this.dot; this.dot -= .01; }
Regardless, the point is that I should be able to look at the loop condition to know when the loop will terminate. Having vague flags does not give me a feel for how or when they’ll be updated. Not to mention, they have to be updated regularly in various places in the code. Failure to do so will result in bugs.
Going a Bit Deeper: Loop Invariants
At this point, you may not be totally sold on removing flags from your code. That’s fine. However, let me share with you one last concept: loop invariants.
A loop invariant is a statement that is true about a loop before, during, and after the loop. In my experience, these types of statements are somewhat hard to write, but they have a bit of payoff. See, with a strong loop invariant, we can trace over a loop in a single step (without ever seeing inside the loop). For instance, here’s a simple loop invariant:
int x = 3; int y = 7; // x + y = 10 while (x != 6) { ... }
Here, we have a loop where we don’t know what’s going on inside. However, we do know that two variables are changing: x
and y
. We know that because the loop condition depends on x
becoming equal to 6 and x
and y
have to sum to 10. With this information, we can guarantee that the final value of x
is 6 (assuming the loop terminates). Therefore, y
has to be 4 to maintain the loop invariant.
To solve for y
, we needed two pieces of information: the loop invariant and the loop condition. If for some reason the loop condition lacks critical information, the loop invariant becomes useless. For example, what if we had the same loop but it looked like this:
int x = 3; int y = 7; // x + y = 10 while (!stop) { ... }
In this case, we have no clue what stop
means, how it’s calculated, or how its updated. Therefore, we don’t know when the loop will terminate, so we can’t solve for either x
or y
. In fact, all we know is that the two variables will sum to 10, so the loop invariant itself is somewhat useless.
That said, people in general don’t write loop invariants, so who cares? Well, loop invariants force us to write explicit loop conditions which actually leads to better formed loops. I know the logic is somewhat circular, but the idea that the loop condition and loop body are separated follows a broader best practice: decoupling.
When we decouple the loop body from the loop condition, we always know where to look when trouble arises. Specifically, we know exactly when our loop will terminate strictly by looking at the condition. Therefore, if we have an infinite loop, chances are the issue is in the body. Similarly, if we ever need to rethink our loop condition, we don’t have to track down branches and flags in the body to make sure things work out.
Take My Advice With a Grain of Salt
In the world of writing, everything has to follow the trend of clickbait. Naturally, this article is no different. My general approach to software development is to do what makes sense to you. That said, folks won’t read my work if I don’t use sensational headlines like “NEVER USE FLAGS” or something like that. In this case, feel free to use them as needed. My only advice is that you may find loops easier to write and debug if your loop condition is all in one place.
With that said, thanks for sticking around! If you like this sort of thing, check out some of these related articles:
- Improve Code Readability by Using Parameter Modes
- Summarizing Community Beliefs Around Commenting Code
Likewise, consider showing your support by heading over to my list of ways to grow the site. There, you’ll find links to my newsletter and Patreon. Otherwise, take care!
Recent Posts
It seems I'm in my mentorship arc because I can't stop writing about how to support students. Today, we're going to tackle one of the more heartbreaking concerns that students have: the idea that...
For my friends in the humanities, it probably comes as a surprise, but our STEM programs are really allergic to open-ended projects. As a result, I figured I would pitch them today.