CodeRigor

FREE LESSON

Functions, loops & explicit outcomes

Use multiple returns, early exits and range loops without losing a search result or sentinel boundary.

Go functions declare parameter types and result types. Multiple return values are a routine way to express related outcomes. A search can return an index and a bool: index zero is perfectly usable when the bool is true. If you know Python tuple returns, the caller experience is familiar, but Go checks both the number and the types of results at compilation. C++ users can think of a small explicit product of results without a custom result struct.

Use named functions to separate a policy from a traversal. Read the function's contract before choosing its loop. Finding the first qualifying element means returning at the first match. Finding every qualifying element would need different storage and a different contract. Code that searches correctly but returns the last match still violates a first-match promise.

A search has an explicit missing path
start / 3
A matching index is meaningful only alongside its true validity result.
A search has an explicit missing path
A matching index is meaningful only alongside its true validity result.

One loop keyword, distinct exit operations

for supports a three-part loop, a condition-only loop and range traversal. For a slice, for i, value := range xs supplies an index and an element value. The blank identifier discards a result you do not need. This lesson reads slices as finite sequences; the next lesson examines how they share backing storage. An empty or nil slice simply has no iterations.

An early return leaves the function. break leaves the innermost applicable loop or switch. continue begins the loop's next iteration. A sentinel that ends a record stream therefore needs break, not continue: discarding one marker and consuming its suffix changes the meaning of the stream. Go does not infer the intended meaning of a sentinel from its value.

Go
package main
import "fmt"
func first(xs []int, threshold int) (int, bool) {
    for i, value := range xs {
        if value >= threshold { return i, true }
    }
    return 0, false
}
func main() {
    fmt.Println(first([]int{5, 8}, 5))
    fmt.Println(first(nil, 5))
}

The output is 0 true and 0 false. The same numeric index has a different interpretation because the validity result differs. Assign both results at the call site, check the bool, and only then index the sequence.

Consume only the prefix
start / 2
The first negative value ends consumption; later positive values are outside the prefix.
Consume only the prefix
The first negative value ends consumption; later positive values are outside the prefix.

Small boundaries make the implementation reviewable

Trace a leading sentinel, a trailing sentinel and no sentinel. Include zero: it is not negative. For FirstAtLeast, test an exact threshold tie and repeated matching values. Both are small cases that distinguish an almost-correct comparison from the actual contract. These tasks bound input magnitudes so the lesson can focus on control flow rather than overflow policy.

The build asks for two pure functions with no output or mutation. The fill completes the search predicate. The bug hunts each change one decision: whether ties qualify, and whether a sentinel ends consumption. Treat passing examples as evidence for those decisions, not as a reason to omit the empty case.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on ordinary functions, multiple returns and control flow in the introductory examples. Skip concurrency for this unit; modern module setup is covered later.