Go Short Statement in If — Tasks¶
Instructions¶
Each task includes a description, starter code, expected output, and an evaluation checklist. Use the if-init form idiomatically. When the value must outlive the chain, use a hoisted declaration instead — the right choice is part of each task.
Task 1 — Basic Init Guard¶
Difficulty: Beginner Topic: Single-variable init
Description: Write a program that reads an integer from compute() and uses an if-init form to print "positive", "negative", or "zero" without leaving the value in the surrounding scope.
Starter Code:
package main
import "fmt"
func compute() int { return -3 }
func main() {
// TODO: use if-init to inspect compute()'s result and print the right word.
}
Expected Output (for return value -3):
Evaluation Checklist: - [ ] Uses if x := compute(); ... with a chained else if and else - [ ] x not used after the chain - [ ] All three branches reachable (test by changing compute's return)
Solution
Task 2 — Map Comma-Ok¶
Difficulty: Beginner Topic: Comma-ok in init
Description: Given a map[string]int, write a function report(m map[string]int, key string) that prints "<key> = <value>" if the key is present (regardless of value) and "<key> not in map" otherwise.
Starter Code:
package main
import "fmt"
func report(m map[string]int, key string) {
// TODO
}
func main() {
m := map[string]int{"a": 0, "b": 5}
report(m, "a")
report(m, "c")
}
Expected Output:
Evaluation Checklist: - [ ] Uses if v, ok := m[key]; ok { ... } else { ... } - [ ] Distinguishes "present-and-zero" from "absent" (the a case) - [ ] No leakage of v or ok beyond the chain
Solution
Task 3 — Type Assertion Guard¶
Difficulty: Beginner Topic: Type assertion in init
Description: Implement describe(i any) that prints type-specific descriptions for int, string, and []int, and a generic message for any other type. Use chained if-init type assertions.
Starter Code:
package main
import "fmt"
func describe(i any) {
// TODO
}
func main() {
describe(42)
describe("Go")
describe([]int{1, 2, 3})
describe(3.14)
}
Expected Output:
Evaluation Checklist: - [ ] Each type check uses if v, ok := i.(T); ok { ... } - [ ] Returns/prints early on the first matching type - [ ] Falls through to "unknown type" otherwise
Solution
Task 4 — Channel Receive Guard¶
Difficulty: Beginner Topic: Comma-ok with channels in init
Description: Implement drain(ch <-chan int) that prints every received value and prints "channel closed" when the channel is drained and closed. Use if v, ok := <-ch; ok inside an infinite for.
Starter Code:
package main
import "fmt"
func drain(ch <-chan int) {
// TODO
}
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
drain(ch)
}
Expected Output:
Evaluation Checklist: - [ ] Uses for { if v, ok := <-ch; ok { ... } else { return } } - [ ] Prints each received value - [ ] Returns when the channel is closed
Solution
Task 5 — Switch With Init¶
Difficulty: Beginner Topic: Switch-init parallel
Description: Write dayKind(day time.Weekday) string that returns "weekend" for Saturday/Sunday and "weekday" otherwise. Use a tagless switch with init.
Starter Code:
package main
import (
"fmt"
"time"
)
func dayKind(day time.Weekday) string {
// TODO: switch d := day; { case ... }
return ""
}
func main() {
fmt.Println(dayKind(time.Saturday))
fmt.Println(dayKind(time.Wednesday))
}
Expected Output:
Evaluation Checklist: - [ ] Uses switch d := day; { case ... } - [ ] Both cases reachable - [ ] d not used after the switch
Solution
Task 6 — Refactor: Avoid Err-Shadowing¶
Difficulty: Intermediate Topic: := vs = in init
Description: This function silently loses errors. Refactor so that errors are preserved.
Starter Code:
package main
import (
"errors"
"fmt"
)
func op(i int) error {
if i%2 == 1 {
return fmt.Errorf("odd: %d", i)
}
return nil
}
func runAll() error {
var err error
for i := 0; i < 4; i++ {
if err := op(i); err != nil {
fmt.Println("logged:", err)
}
}
return err
}
func main() {
e := runAll()
fmt.Println("returned:", e)
_ = errors.New
}
Bug: The init's err := shadows the outer err. runAll returns nil even though op(1) and op(3) failed.
Expected Output (after fix): returned should be the last logged error.
Evaluation Checklist: - [ ] Identifies the shadowing bug - [ ] Fixes by using = to assign to the outer err - [ ] Last error is preserved in the return value
Solution
Output:Task 7 — Choosing Init vs Hoisted¶
Difficulty: Intermediate Topic: When NOT to use init form
Description: Refactor parseAndUse(raw string) so it is idiomatic. The function must parse raw as an integer, error out on failure, and double the parsed value if it is positive.
Starter Code:
package main
import (
"fmt"
"strconv"
)
func parseAndUse(raw string) (int, error) {
if n, err := strconv.Atoi(raw); err != nil {
return 0, err
}
if n > 0 { // ??
return n * 2, nil
}
return n, nil
}
func main() {
fmt.Println(parseAndUse("21"))
}
Bug: n is declared in the if-init; it does not exist outside. The function does not compile.
Evaluation Checklist: - [ ] Recognizes that n must outlive the err check - [ ] Hoists the declaration: n, err := strconv.Atoi(raw); if err != nil { ... } - [ ] Function compiles and returns 42 for input "21"
Solution
func parseAndUse(raw string) (int, error) {
n, err := strconv.Atoi(raw)
if err != nil {
return 0, err
}
if n > 0 {
return n * 2, nil
}
return n, nil
}
Task 8 — Validation Chain¶
Difficulty: Intermediate Topic: Layered if-init for validation
Description: Implement validateEmail(s string) error returning errors for: empty string, missing @, missing domain dot. Use chained else if with init.
Starter Code:
package main
import (
"errors"
"fmt"
"strings"
)
func validateEmail(s string) error {
// TODO
return nil
}
func main() {
cases := []string{"", "no-at", "user@nodot", "ok@example.com"}
for _, c := range cases {
fmt.Printf("%q -> %v\n", c, validateEmail(c))
}
}
Expected Output:
"" -> empty email
"no-at" -> missing @
"user@nodot" -> missing dot in domain
"ok@example.com" -> <nil>
Evaluation Checklist: - [ ] Uses chained if/else if with init for each check - [ ] Each init scopes its variable to its check - [ ] Returns the right error for each case
Solution
Task 9 — Type Switch With Init¶
Difficulty: Intermediate Topic: Type switch + init
Description: Write total(values []any) int that sums the integer values, doubles the length of any string values, and ignores other types. Use a type switch with init inside the loop.
Starter Code:
package main
import "fmt"
func total(values []any) int {
sum := 0
for _, v := range values {
// TODO: use type switch with init
_ = v
}
return sum
}
func main() {
fmt.Println(total([]any{1, "ab", 2, 3.14, "cd", 7}))
}
Expected Output:
(Compute: 1 + 2len("ab")=4 + 2 + (skip 3.14) + 2len("cd")=4 + 7 = 18.)
Evaluation Checklist: - [ ] Uses switch x := v; t := x.(type) { ... } (or simply switch t := v.(type) if init not needed) - [ ] Adds int values directly - [ ] Adds 2*len for strings - [ ] Skips other types
Solution
(The init `x := v` is artificial here since `v` is already in scope; a more typical real use is when computing a value to feed the type switch.)Task 10 — Switch-Init for Dispatch¶
Difficulty: Intermediate Topic: Switch-init avoids recomputation
Description: Implement messageHour(m Message) string that returns "morning", "afternoon", "evening", or "night" based on m.Time().Hour(). Use switch h := m.Time().Hour(); { case ... } so the call to Time().Hour() runs once.
Starter Code:
package main
import (
"fmt"
"time"
)
type Message struct {
t time.Time
}
func (m Message) Time() time.Time { return m.t }
func messageHour(m Message) string {
// TODO
return ""
}
func main() {
m := Message{t: time.Date(2025, 1, 1, 14, 0, 0, 0, time.UTC)}
fmt.Println(messageHour(m))
}
Expected Output:
Evaluation Checklist: - [ ] Calls m.Time().Hour() once (in the switch init) - [ ] All four cases handled - [ ] h not visible after the switch
Solution
Task 11 — Detecting the Shadowing Trap¶
Difficulty: Hard Topic: Named returns + init
Description: Identify and fix the bug. Both code paths should return the actual computed value.
Starter Code:
package main
import "fmt"
func compute() (int, error) { return 100, nil }
func work() (n int, err error) {
if n, err := compute(); err != nil {
return n, err
}
return n, nil
}
func main() {
n, err := work()
fmt.Println(n, err) // expected: 100 <nil>; actual: 0 <nil>
}
Bug: The init's := shadows the named returns. The inner n and err are local to the if's implicit block. When err == nil, control falls past the if; the body's return n, nil reads the outer named n, which is still its zero value (0).
Evaluation Checklist: - [ ] Identifies the shadowing - [ ] Fixes by using = (assignment) instead of := - [ ] Function returns 100 on success
Solution
func work() (n int, err error) {
if n, err = compute(); err != nil {
return n, err
}
return n, nil
}
Task 12 — Building a Result Slice¶
Difficulty: Hard Topic: Loop + if-init
Description: Given sources []func() (int, error), return a slice containing the successful results. Each function may fail; failures are logged but do not stop iteration.
Starter Code:
package main
import (
"errors"
"fmt"
"log"
)
func collect(sources []func() (int, error)) []int {
out := make([]int, 0, len(sources))
// TODO
return out
}
func main() {
s := []func() (int, error){
func() (int, error) { return 10, nil },
func() (int, error) { return 0, errors.New("boom") },
func() (int, error) { return 20, nil },
}
fmt.Println(collect(s)) // [10 20]
_ = log.Println
}
Expected Output:
(With "boom" logged vialog.Println.) Evaluation Checklist: - [ ] Iterates sources - [ ] Uses if v, err := f(); err != nil { log... } else { out = append(out, v) } - [ ] No leakage of v, err outside each iteration's check
Solution
Task 13 — Cache With Fallback¶
Difficulty: Hard Topic: Multiple if-inits in sequence
Description: Implement Cache.Get(key string) (Value, error) that first checks a local map; if missing, fetches from a remote interface; returns an error if remote fails.
Starter Code:
package main
import (
"errors"
"fmt"
)
type Value struct{ N int }
type remote interface {
Fetch(key string) (Value, error)
}
type fakeRemote struct{}
func (fakeRemote) Fetch(key string) (Value, error) {
if key == "x" {
return Value{N: 99}, nil
}
return Value{}, errors.New("remote miss")
}
type Cache struct {
local map[string]Value
remote remote
}
func (c *Cache) Get(key string) (Value, error) {
// TODO: check local; on miss, fetch from remote and populate local.
return Value{}, nil
}
func main() {
c := &Cache{local: map[string]Value{"a": {N: 1}}, remote: fakeRemote{}}
fmt.Println(c.Get("a"))
fmt.Println(c.Get("x"))
fmt.Println(c.Get("?"))
}
Expected Output:
Evaluation Checklist: - [ ] Uses if v, ok := c.local[key]; ok { return v, nil } - [ ] Uses if v, err := c.remote.Fetch(key); err == nil { c.local[key] = v; return v, nil } - [ ] Returns the remote's error on failure
Solution
Task 14 — Reject Heavy Init¶
Difficulty: Hard Topic: Style judgment
Description: This code passes lint but is hard to read. Refactor without changing behavior.
Starter Code:
package main
import "fmt"
type Result struct {
Items []int
Total int
}
func slowQuery() Result { return Result{Items: []int{1, 2, 3}, Total: 6} }
func summary() string {
if r := slowQuery(); r.Total > 0 && len(r.Items) > 0 && r.Total/len(r.Items) > 1 {
return fmt.Sprintf("avg %d", r.Total/len(r.Items))
}
return "empty"
}
func main() {
fmt.Println(summary())
}
Issue: Heavy work (slowQuery) sits in the init position; the boolean condition is long and recomputes r.Total/len(r.Items) twice.
Evaluation Checklist: - [ ] Hoists slowQuery() out of the init - [ ] Computes avg once - [ ] Reads more clearly without losing tight scope
Solution
Now `r` and `avg` are clearly named, `slowQuery` is highlighted as the major operation, and there is no duplicated arithmetic. The cost is two extra lines and `r` lives past the conditional — acceptable for clarity.Task 15 — Predict the Output¶
Difficulty: Hard Topic: Scope reasoning
Description: Predict the exact output without running the program. Then verify.
Code:
package main
import "fmt"
func main() {
x := 1
if x := x + 1; x > 1 {
fmt.Println("A:", x)
if x := x + 1; x > 2 {
fmt.Println("B:", x)
}
fmt.Println("C:", x)
}
fmt.Println("D:", x)
}
Self-check: - [ ] What does each line print? - [ ] Which x does each line refer to?