Knaph

Control Flow and Functions18 of 623 min read

If

Go deliberately has fewer control-flow keywords than most languages. There's no while, no do-while, no ternary operator. Once you know why, it stops feeling like a missing feature and starts feeling like one less thing to decide between.

if

age := 20
 
if age >= 18 {
	fmt.Println("adult")
} else if age >= 13 {
	fmt.Println("teen")
} else {
	fmt.Println("child")
}
Go

Two things jump out coming from most other languages: no parentheses around the condition, and the braces are mandatory — you can't write a one-line if without them. Both are enforced by the compiler, not just a style guide, which means every Go codebase you read looks like this one.

if also accepts an initializer statement — a short statement that runs right before the condition is checked, scoped to just the if/else chain. It looks unfamiliar at first, so let's build up to it in pieces, using only what you already know: := and comparisons.

Here's the same code written two ways. First, as two separate lines:

x := 10
if x > 5 {
	fmt.Println("big:", x)
} else {
	fmt.Println("small:", x)
}
Go

Now with the initializer folded into the if, using a semicolon to separate the setup statement from the condition:

if x := 10; x > 5 {
	fmt.Println("big:", x)
} else {
	fmt.Println("small:", x)
}
Go

Read it as two statements glued together — x := 10 runs first, then x > 5 is checked as the condition, exactly like the two-line version above. The semicolon is the seam: everything before it is the initializer, everything after it is the condition.

Why fold it in at all? Scope. A variable declared in the initializer only exists inside the if/else if/else chain it belongs to — not in the rest of the function:

if x := 10; x > 5 {
	fmt.Println("big:", x)
}
fmt.Println(x) // compile error: undefined: x
Go

With the two-line version, x would still be sitting in the function's scope after the if block ends — available to be misused, or to collide with another variable named x later on.

This becomes genuinely useful once a statement can produce more than one value — which is exactly what you'll see in the upcoming Functions and Errors lessons later in this module, in patterns like:

if err := doSomething(); err != nil {
	return err
}
Go

Here err only needs to exist long enough to be checked, then it's gone — the next line down can declare its own err := ... with no conflict. You'll see this constantly once functions and errors are in play; for now, the thing to hold onto is just this: if can run a short setup statement right before its condition, and whatever that statement declares only lives inside that if/else chain.

No ternary operator

Python has x if cond else y; Go has nothing shorter than a full if/else. This is intentional — the language designers left it out on purpose to avoid the nested-ternary readability problem, and the idiomatic replacement is just to write the if:

status := "child"
if age >= 18 {
	status = "adult"
}
Go

It's a few more lines than a ternary. Once you've read enough Go, you'll find you stop missing it.

Test your understanding

Sign in to test your understanding of this lesson and track your progress.

Ask an AI

Open a ready-made prompt in ChatGPT or Claude — just press Enter.