Break that confuses: how Go flipped the familiar "switch rule"
\n\nIf you've ever written code in C, C++, Java, or JavaScript, you know the ritual: after each case in a switch, you must put a break. Otherwise — welcome to the hell of "fall-through", where code executes from one case to another until it encounters a break or the end of the construct.
Now imagine: you switch to Go, write a switch, and out of habit put a break at the end of each case. The code works. But you start wondering: "Is it even needed? And what happens if I remove it?".
This question is one of the most popular on Stack Overflow in the Go section. And the answer surprises beginners: in Go, break in switch and select is not mandatory, but it exists and does something different. Let's figure it out with examples so you never get confused again.
\n\nGo vs. C: "inverted" switch
\n\nIn classic C-like languages, switch works like this:
// Pseudo-C code\nint x = 2;\nswitch(x) {\n case 1:\n printf("One");\n break; // Without break — will fall through to the next case\n case 2:\n printf("Two");\n break;\n default:\n printf("Other");\n}\n// Output: "Two"\n\nRemove the break after case 1 — and the program will print "OneTwo". This behavior is called fall-through, and it often leads to bugs.
In Go, they decided: "Enough of that!" By default, each case in a switch automatically terminates, and break is not needed:
package main\n\nimport "fmt"\n\nfunc main() {\n x := 2\n switch x {\n case 1:\n fmt.Println("One")\n // break not needed — we'll exit the switch\n case 2:\n fmt.Println("Two")\n // break not needed\n default:\n fmt.Println("Other")\n }\n // Output: "Two"\n}\n\nBut if you still put a break — nothing bad happens, the code will just exit the switch earlier. However, why add an unnecessary break if this is the default behavior?.
What about fall-through?
\n\nIf you need to "fall through" to the next case, Go provides an explicit keyword — fallthrough:
switch x {\ncase 1:\n fmt.Println("One")\n fallthrough // Explicitly saying: "I want to go to the next case"\ncase 2:\n fmt.Println("Two")\ndefault:\n fmt.Println("Other")\n}\n// When x=1 it will output: "One" and "Two"\n\nNote: fallthrough only works for the next case and not as a chain. This is done intentionally to avoid accidental errors.
Select: the multi-channel "brother" of switch
\n\nGo has a unique construct — select. It resembles switch but only works with channels. And here break also exists, but with a twist.
package main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n ch1 := make(chan string)\n ch2 := make(chan string)\n\n go func() {\n time.Sleep(1 * time.Second)\n ch1 <- "Hello from channel 1"\n }()\n go func() {\n time.Sleep(2 * time.Second)\n ch2 <- "Hello from channel 2"\n }()\n\n select {\n case msg1 := <-ch1:\n fmt.Println(msg1)\n break // Exit from select\n case msg2 := <-ch2:\n fmt.Println(msg2)\n break // Exit from select\n case <-time.After(3 * time.Second):\n fmt.Println("Timeout")\n }\n}\n\nHere break is also not mandatory — select terminates after executing one case. But why is it needed? The answer: to break out of an outer loop!