Control flow
Switch
How JavaScript's switch/case compares to Go (no fallthrough by default, explicit fallthrough), Rust's match (no fallthrough concept), Swift (like Go), and Java (like JS).
- Minimum versions
- Node.js ≥ 12.20Go ≥ 1.0Rust ≥ 1.0Swift ≥ 2.0Java ≥ 25
- Verified on
- Node.js 24.12.0Go 1.27.1Rust 1.98.1Swift 6.2.4Java 25.0.4.1
Node.js code is an ES module: save it as .mjs or set "type": "module" in package.json.
Go's switch looks like JavaScript's but the default behavior is exactly reversed: JavaScript falls through to the next case unless you break, while Go stops after the matching case unless you explicitly write fallthrough. Swift behaves exactly like Go (no fallthrough by default, with an explicit fallthrough keyword). Rust doesn't have a switch at all — it has match, and match has no fallthrough concept in any form. Java's classic :-style switch behaves like JavaScript instead: it falls through by default and needs break to stop — though Java 14 added an arrow-based switch expression (->) that never falls through.
Non-fallthrough vs. fallthrough switch
const value = 'b'
switch(value) {
case 'a':
console.log('A')
break
case 'b':
console.log('B')
break
case 'c':
console.log('C')
break
default:
console.log('first default')
}
switch(value) {
case 'a':
console.log('A - falling through')
case 'b':
console.log('B - falling through')
case 'c':
console.log('C - falling through')
default:
console.log('second default')
}package main
import "fmt"
func main() {
value := "b"
switch value {
case "a":
fmt.Println("A")
case "b":
fmt.Println("B")
case "c":
fmt.Println("C")
default:
fmt.Println("first default")
}
switch value {
case "a":
fmt.Println("A - falling through")
fallthrough
case "b":
fmt.Println("B - falling through")
fallthrough
case "c":
fmt.Println("C - falling through")
fallthrough
default:
fmt.Println("second default")
}
}fn main() {
let value = "b";
match value {
"a" => println!("A"),
"b" => println!("B"),
"c" => println!("C"),
_ => println!("first default"),
}
// Rust has no "fallthrough": every `match` arm is self-contained, and there's no way to
// "flow" into the next one. Multiple patterns sharing one body are merged with `|` instead —
// a completely different thing from Go/JS's "flow through several bodies, accumulating output".
match value {
"a" | "b" | "c" => println!("A, B or C"),
_ => println!("default"),
}
}let value = "b"
switch value {
case "a":
print("A")
case "b":
print("B")
case "c":
print("C")
default:
print("first default")
}
switch value {
case "a":
print("A - falling through")
fallthrough
case "b":
print("B - falling through")
fallthrough
case "c":
print("C - falling through")
fallthrough
default:
print("second default")
}void main() {
String value = "b";
switch (value) {
case "a":
IO.println("A");
break;
case "b":
IO.println("B");
break;
case "c":
IO.println("C");
break;
default:
IO.println("first default");
}
switch (value) {
case "a":
IO.println("A - falling through");
case "b":
IO.println("B - falling through");
case "c":
IO.println("C - falling through");
default:
IO.println("second default");
}
}# Go / Swift / Java
B
B - falling through
C - falling through
second default
# Rust (match has no fallthrough, see note)
B
A, B or CKey differences
| Node.js | Go | Rust | Swift | Java (classic switch) |
|
|---|---|---|---|---|---|
| Default after a matching case | falls through to the next case | stops (implicit break) | stops — there's no other concept | stops (implicit break) | falls through to the next case |
| To stop | you must write break |
nothing needed, it's the default | nothing needed, it's the default | nothing needed, it's the default | you must write break |
| To fall through | it already does | you must write fallthrough explicitly |
not possible — use an or-pattern | to merge cases instead |
you must write fallthrough explicitly |
it already does |
Reference: github.com/miguelmota/golang-for-nodejs-developers#switch