Polyglot Primer

Chapter 4: Control Flow

Branching and looping. Go has the fewest keywords (one loop, for); Java and JavaScript have the most (for, while, do/while, enhanced for); Python has for/while plus an else clause on loops.

Overview

ConstructGoJavaJavaScriptPython
if / elseyesyesyesif / elif / else
Condition parenthesesnonerequiredrequirednone
Braces requiredalwaysoptional (don't skip)optional (don't skip)indentation
Loop keywordsfor onlyfor, while, do-while, for-eachfor, while, do-while, for...of, for...infor, while
switchexpression-less or value switch, no fallthroughswitch statement and expressionswitch (strict equality, falls through)match (3.10+, structural)
Ternarynone? :? :x if c else y
goto / labelsgoto and labelslabelled break/continuelabelled break/continuenone

if / else

if x > 10 {
    fmt.Println("big")
} else if x > 5 {
    fmt.Println("medium")
} else {
    fmt.Println("small")
}

// init statement scoped to the if (very idiomatic)
if err := doThing(); err != nil {
    return err
}
if (x > 10) {
    System.out.println("big");
} else if (x > 5) {
    System.out.println("medium");
} else {
    System.out.println("small");
}
if (x > 10) {
  console.log("big");
} else if (x > 5) {
  console.log("medium");
} else {
  console.log("small");
}
if x > 10:
    print("big")
elif x > 5:
    print("medium")
else:
    print("small")

Go quirk: the else must be on the same line as the closing }. The init statement (if v, ok := m[k]; ok {) limits variable scope to the if chain - use it constantly.

Loops

Counting loop

for i := 0; i < 5; i++ {
    fmt.Println(i)
}
for i := range 5 {          // Go 1.22+: range over an integer
    fmt.Println(i)
}
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}
for (let i = 0; i < 5; i++) {
  console.log(i);
}
for i in range(5):          # 0..4
    print(i)
for i in range(2, 10, 3):   # start, stop (exclusive), step -> 2, 5, 8
    print(i)

While-style loop

for n > 0 {                 // Go's "while"
    n /= 2
}
for {                       // infinite loop; exit with break/return
    if done() { break }
}
while (n > 0) { n /= 2; }
do { n++; } while (n < 10);       // body runs at least once
while (n > 0) { n = Math.floor(n / 2); }
do { n++; } while (n < 10);
while n > 0:
    n //= 2
# no do-while; use `while True:` with a break

Iterating collections

nums := []int{10, 20, 30}
for i, v := range nums { fmt.Println(i, v) }
for _, v := range nums { fmt.Println(v) }       // _ discards the index
for k, v := range someMap { }                   // random order!
for (int v : nums) { }                          // for-each over arrays/Iterables
for (var e : map.entrySet()) { e.getKey(); e.getValue(); }
for (const v of nums) { }                       // values of any iterable
for (const [i, v] of nums.entries()) { }        // index + value
for (const key in obj) { }                      // KEYS of an object - avoid on arrays
nums.forEach((v, i) => { });                    // cannot break out of this
for v in nums: ...
for i, v in enumerate(nums): ...
for k, v in some_dict.items(): ...
for a, b in zip(xs, ys): ...                    # parallel iteration

Key differences:

break, continue and labels

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 2 { continue outer }
        if i == 2 { break outer }
    }
}
outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 2) continue outer;
        if (i == 2) break outer;
    }
}
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (j === 2) continue outer;
    if (i === 2) break outer;
  }
}
# no labels. Extract a function and return, or use a flag / for-else.

Loop else (Python only)

for n in numbers:
    if n == target:
        print("found")
        break
else:
    print("not found")      # runs only if the loop finished without break

Handy, but many people find it confusing; a helper function that returns early is usually clearer.

switch / match

switch day {
case "sat", "sun":          // multiple values, no fallthrough by default
    fmt.Println("weekend")
case "fri":
    fmt.Println("almost")
    fallthrough             // explicit, rarely used
default:
    fmt.Println("weekday")
}

switch {                    // no tag: a cleaner if/else chain
case x < 0:
    fmt.Println("neg")
case x == 0:
    fmt.Println("zero")
default:
    fmt.Println("pos")
}

switch v := val.(type) {    // type switch
case int:    fmt.Println("int", v)
case string: fmt.Println("string", v)
}
// Java 14+ switch expression: arrows, no fallthrough, must be exhaustive
String kind = switch (day) {
    case "sat", "sun" -> "weekend";
    case "fri" -> "almost";
    default -> "weekday";
};

// Java 21: pattern matching in switch
String describe(Object o) {
    return switch (o) {
        case Integer i when i > 10 -> "big int " + i;
        case Integer i -> "int " + i;
        case String s -> "string " + s;
        case null -> "null";
        default -> "other";
    };
}
switch (day) {
  case "sat":
  case "sun":
    console.log("weekend");
    break;                  // forget this and execution FALLS THROUGH
  default:
    console.log("weekday");
}
// JS has no pattern matching; use if/else chains, lookup objects, or `switch (true)`.
match command.split():                       # Python 3.10+
    case ["go", direction]:
        print("going", direction)
    case ["quit" | "exit"]:
        print("bye")
    case [x, y] if x == y:
        print("pair")
    case _:
        print("unknown")

Order of power: Python match (structural: unpacks sequences, dicts and classes) and Java 21 patterns (types, records) are the richest; Go's type switch covers type dispatch; JS switch is the most primitive and uses ===.

Ternary and conditional values

// no ternary: use if/else with a variable
label := "odd"
if n%2 == 0 { label = "even" }
String label = n % 2 == 0 ? "even" : "odd";
const label = n % 2 === 0 ? "even" : "odd";
const name = user?.name ?? "anonymous";    // optional chaining + nullish coalescing
label = "even" if n % 2 == 0 else "odd"
name = user.name if user else "anonymous"
x = a or "default"                           # `or` returns an operand, not a bool

Early return and guard clauses

All four languages reward flat code over nested code:

func process(u *User) error {
    if u == nil { return errors.New("nil user") }
    if !u.Active { return errors.New("inactive") }
    // happy path stays at the left margin
    return nil
}

The same guard-clause pattern is idiomatic in Java, JS and Python. Go enforces it culturally: if err != nil { return err } is everywhere.

Quirks and gotchas

What is idiomatic

Checklist

Exercises

Implement FizzBuzz (1 to 100) in each language, three ways:

  1. Using a counting loop and if/else.
  2. Using switch (Go tagless, Java arrow expression, JS switch (true), Python match).
  3. Using the most idiomatic collection-style iteration you can (Go range-over-int, Java IntStream, JS Array.from, Python comprehension).

Then write a function firstDuplicate(nums) that returns the first repeated number or "none", using a labelled break (Go/Java/JS) or Python's loop else.