The bug
The program should greet three students and then announce the class once:
Hey, Andy
Hey, Mike
Hey, Mary
Let's start the class!
The code we start from is:
students := []string{"Andy", "Mike", "Mary"}
for _, student := range students {
fmt.Println("Hey, " + student)
fmt.Println("Let's start the class!")
}
It prints the announcement after every greeting:
Hey, Andy
Let's start the class!
Hey, Mike
Let's start the class!
Hey, Mary
Let's start the class!
Why it happens
The loop body is everything between the braces, and it runs once per element of students. Both fmt.Println calls sit in that body, so both run three times, in the order they are written. The first call uses student, so it belongs in the loop. The second never mentions student. It is a single announcement that should happen after the greetings are finished.
The fix
Close the loop right after the greeting, and put the announcement on the next line, outside the braces:
students := []string{"Andy", "Mike", "Mary"}
for _, student := range students {
fmt.Println("Hey, " + student)
}
fmt.Println("Let's start the class!")
Now the flow is: the loop visits Andy, Mike and Mary and prints a greeting for each. Once there are no more elements, the loop ends and the program moves on to the next line, which runs one time.
The blank line is not what fixes it
The blank line between the loop and the announcement is only for readability. What changed is the closing brace: it moved up so that it comes before the announcement. In Go the braces define the loop body, so moving a closing brace is exactly the kind of change that moves a statement in or out of a loop.
Go details in this code
- The blank identifier
_takes the index thatrangeproduces. Writing a name there that you never use would be a compile error. - The variable
studentexists only inside the loop. If you tried to print it after the closing brace, the compiler would say it is undefined. - The opening brace of
formust be on the same line as the loop header. Go does not accept it on the next line.
A quick check
Ask of each line: how many times should it run? The greeting should run three times, once per student. The announcement should run once. Lines that run once go outside the braces, lines that run once per element go inside. This habit solves most loop-placement bugs before you run the code.
Tracing both versions
In the buggy version the pass for Andy prints the greeting and then the announcement. The pass for Mike does the same, and so does the pass for Mary. That is six lines, with the announcement appearing three times.
In the fixed version each pass prints only a greeting, so the loop produces three lines. The program then reaches the announcement exactly once. Four lines in total, which matches the expected output.
What if the slice changes?
Suppose the class has five students. The fixed program prints five greetings and one announcement without any other edit. With an empty slice it skips the loop body entirely and still prints the announcement, which is the right behaviour for a class that starts whether or not anyone is listed.
The opposite mistake
The reverse bug is just as common: a line that needs the loop variable ends up outside the loop. If the greeting were placed after the closing brace, the compiler would complain that student is undefined, and the loop body would be left without a use for its variable. Go catches that one for you. A statement in the wrong place that does not use the loop variable, like the announcement here, compiles fine and only shows up as wrong output, so you have to catch it by comparing the output with what you expect.