const and iota — Optimization Exercises¶
Overview¶
This file contains 12 optimization exercises. Each exercise starts with code that works but is not idiomatic, safe, or efficient. Your task is to refactor it using the best practices around const and iota.
For each exercise: 1. Read the "Before" code and identify the problems 2. Write your improved version 3. Check the solution
Exercises are ordered from basic to advanced.
Exercise 1 — Replace Magic Numbers in a Validation Function¶
Problem¶
The function works, but magic numbers are scattered throughout, making the code hard to maintain and understand.
Before¶
package main
import (
"errors"
"fmt"
)
func validateUser(age int, nameLen int, score float64) error {
if age < 13 {
return errors.New("user too young")
}
if age > 120 {
return errors.New("invalid age")
}
if nameLen < 2 {
return errors.New("name too short")
}
if nameLen > 50 {
return errors.New("name too long")
}
if score < 0.0 {
return errors.New("score cannot be negative")
}
if score > 100.0 {
return errors.New("score exceeds maximum")
}
return nil
}
func main() {
err := validateUser(25, 8, 87.5)
fmt.Println(err)
err = validateUser(10, 8, 87.5)
fmt.Println(err)
}
Goals¶
- Extract all magic numbers into named constants
- Group related constants together
- Improve readability without changing behavior
Solution
package main
import (
"errors"
"fmt"
)
const (
MinAge = 13
MaxAge = 120
)
const (
MinNameLength = 2
MaxNameLength = 50
)
const (
MinScore = 0.0
MaxScore = 100.0
)
func validateUser(age int, nameLen int, score float64) error {
if age < MinAge {
return errors.New("user too young")
}
if age > MaxAge {
return errors.New("invalid age")
}
if nameLen < MinNameLength {
return errors.New("name too short")
}
if nameLen > MaxNameLength {
return errors.New("name too long")
}
if score < MinScore {
return errors.New("score cannot be negative")
}
if score > MaxScore {
return errors.New("score exceeds maximum")
}
return nil
}
func main() {
err := validateUser(25, 8, 87.5)
fmt.Println(err)
err = validateUser(10, 8, 87.5)
fmt.Println(err)
}
Exercise 2 — Replace Parallel Boolean Flags with a Bitmask¶
Problem¶
A function accepts five independent boolean parameters. This is verbose, hard to read, and doesn't scale.
Before¶
package main
import "fmt"
type Options struct {
Verbose bool
Debug bool
DryRun bool
Force bool
Quiet bool
}
func run(name string, opts Options) {
if opts.Verbose {
fmt.Println("[VERBOSE] Running:", name)
}
if opts.Debug {
fmt.Println("[DEBUG] Debug mode on")
}
if opts.DryRun {
fmt.Println("[DRY RUN] No changes will be made")
}
if opts.Force {
fmt.Println("[FORCE] Forcing operation")
}
}
func main() {
run("deploy", Options{Verbose: true, DryRun: true})
run("reset", Options{Force: true, Verbose: true})
}
Goals¶
- Replace the
Optionsstruct with aFlagbitmask type using1 << iota - Update
runto acceptFlaginstead ofOptions - Usage should be:
run("deploy", FlagVerbose | FlagDryRun)
Solution
package main
import "fmt"
type Flag uint
const (
FlagVerbose Flag = 1 << iota // 1
FlagDebug // 2
FlagDryRun // 4
FlagForce // 8
FlagQuiet // 16
)
func run(name string, flags Flag) {
if flags&FlagVerbose != 0 {
fmt.Println("[VERBOSE] Running:", name)
}
if flags&FlagDebug != 0 {
fmt.Println("[DEBUG] Debug mode on")
}
if flags&FlagDryRun != 0 {
fmt.Println("[DRY RUN] No changes will be made")
}
if flags&FlagForce != 0 {
fmt.Println("[FORCE] Forcing operation")
}
}
func main() {
run("deploy", FlagVerbose|FlagDryRun)
run("reset", FlagForce|FlagVerbose)
}
Exercise 3 — Add a Named Type to an iota Enum¶
Problem¶
The enum uses raw int constants, losing type safety. Any integer can be passed where a Direction is expected.
Before¶
package main
import "fmt"
const (
North = iota
East
South
West
)
func move(direction int) {
switch direction {
case North:
fmt.Println("Moving north")
case East:
fmt.Println("Moving east")
case South:
fmt.Println("Moving south")
case West:
fmt.Println("Moving west")
}
}
func main() {
move(North)
move(42) // No compile error! Any int is accepted
}
Goals¶
- Add a named type
Direction moveshould accept onlyDirection, not rawintmove(42)should cause a compile error
Solution
package main
import "fmt"
type Direction int
const (
North Direction = iota
East
South
West
)
func (d Direction) String() string {
return [...]string{"North", "East", "South", "West"}[d]
}
func move(direction Direction) {
fmt.Printf("Moving %s\n", direction)
}
func main() {
move(North)
move(West)
// move(42) // now a compile error: cannot use 42 (type int) as type Direction
}
Exercise 4 — Use iota Expression for Byte Sizes¶
Problem¶
Byte size constants are defined manually and are error-prone. A typo could introduce a wrong value.
Before¶
package main
import "fmt"
const KB = 1024
const MB = 1024 * 1024
const GB = 1024 * 1024 * 1024
const TB = 1024 * 1024 * 1024 * 1024
func main() {
fmt.Printf("KB: %d\n", KB)
fmt.Printf("MB: %d\n", MB)
fmt.Printf("GB: %d\n", GB)
fmt.Printf("TB: %d\n", TB)
}
Goals¶
- Define KB, MB, GB, TB, PB using a single
iotaexpression - Use the
1 << (10 * iota)pattern - Add type
ByteSizeand implementString()
Solution
package main
import "fmt"
type ByteSize float64
const (
_ = iota
KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
TB // 1 << 40
PB // 1 << 50
)
func (b ByteSize) String() string {
switch {
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
func main() {
fmt.Println(KB) // 1.00KB
fmt.Println(MB) // 1.00MB
fmt.Println(GB) // 1.00GB
fmt.Println(TB) // 1.00TB
}
Exercise 5 — Add IsValid() to Prevent Invalid Enum Usage¶
Problem¶
The code accepts invalid Status values without any error checking.
Before¶
package main
import "fmt"
type Status int
const (
StatusPending Status = iota
StatusActive
StatusClosed
)
func process(s Status) {
switch s {
case StatusPending:
fmt.Println("Processing pending...")
case StatusActive:
fmt.Println("Processing active...")
case StatusClosed:
fmt.Println("Already closed.")
}
}
func main() {
process(StatusActive)
process(Status(99)) // silently does nothing!
}
Goals¶
- Add
IsValid()method toStatus - Add validation at the start of
process() process(Status(99))should print an error message
Solution
package main
import "fmt"
type Status int
const (
StatusPending Status = iota
StatusActive
StatusClosed
statusCount // unexported sentinel
)
func (s Status) String() string {
switch s {
case StatusPending:
return "Pending"
case StatusActive:
return "Active"
case StatusClosed:
return "Closed"
}
return fmt.Sprintf("Status(%d)", int(s))
}
func (s Status) IsValid() bool {
return s >= StatusPending && s < statusCount
}
func process(s Status) error {
if !s.IsValid() {
return fmt.Errorf("invalid status: %d", int(s))
}
switch s {
case StatusPending:
fmt.Println("Processing pending...")
case StatusActive:
fmt.Println("Processing active...")
case StatusClosed:
fmt.Println("Already closed.")
}
return nil
}
func main() {
if err := process(StatusActive); err != nil {
fmt.Println("Error:", err)
}
if err := process(Status(99)); err != nil {
fmt.Println("Error:", err)
}
}
Exercise 6 — Add a Compile-Time Bounds Check to String()¶
Problem¶
The String() method is correct now, but if someone adds a new constant later and forgets to update String(), the code will silently return a wrong value or panic.
Before¶
package main
import "fmt"
type Planet int
const (
Mercury Planet = iota
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
)
func (p Planet) String() string {
names := []string{
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune",
}
if p < 0 || int(p) >= len(names) {
return fmt.Sprintf("Planet(%d)", int(p))
}
return names[p]
}
func main() {
for p := Mercury; p <= Neptune; p++ {
fmt.Println(p)
}
}
Goals¶
- Add a
planetCountsentinel constant - Change
namesfrom a slice to a fixed-size array[planetCount]string - If
planetCountchanges, the array must update or it won't compile
Solution
package main
import "fmt"
type Planet int
const (
Mercury Planet = iota
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
planetCount // 8
)
// Compile-time check: this array must have exactly planetCount elements.
// If you add a Planet constant without updating this array → compile error.
var planetNames = [planetCount]string{
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune",
}
func (p Planet) String() string {
if p < 0 || p >= planetCount {
return fmt.Sprintf("Planet(%d)", int(p))
}
return planetNames[p]
}
func main() {
for p := Mercury; p <= Neptune; p++ {
fmt.Println(p)
}
}
Exercise 7 — Bit Flag Removal Using AND-NOT¶
Problem¶
The flag removal function uses XOR, which incorrectly re-adds flags that aren't set.
Before¶
package main
import "fmt"
type Perm uint
const (
PermRead Perm = 1 << iota
PermWrite
PermExecute
)
func addPerm(base, flag Perm) Perm { return base | flag }
func removePerm(base, flag Perm) Perm { return base ^ flag } // BUG: XOR
func main() {
p := addPerm(0, PermRead)
p = addPerm(p, PermWrite)
fmt.Println(p) // 3: rw-
p = removePerm(p, PermWrite)
fmt.Println(p) // 1: r--
// Call remove again on an already-removed flag:
p = removePerm(p, PermWrite) // BUG: re-adds PermWrite!
fmt.Println(p) // 3: rw- — WRONG! Should still be r--
}
Goals¶
- Fix
removePermto use&^instead of^ - Verify removal is idempotent (calling it twice has the same result as once)
- Add a
has(base, flag Perm) boolhelper
Solution
package main
import "fmt"
type Perm uint
const (
PermRead Perm = 1 << iota // 1
PermWrite // 2
PermExecute // 4
)
func (p Perm) String() string {
r, w, x := "-", "-", "-"
if p&PermRead != 0 {
r = "r"
}
if p&PermWrite != 0 {
w = "w"
}
if p&PermExecute != 0 {
x = "x"
}
return r + w + x
}
func addPerm(base, flag Perm) Perm { return base | flag }
func removePerm(base, flag Perm) Perm { return base &^ flag } // FIXED: AND-NOT
func hasPerm(base, flag Perm) bool { return base&flag != 0 }
func main() {
p := addPerm(0, PermRead)
p = addPerm(p, PermWrite)
fmt.Println(p) // rw-
p = removePerm(p, PermWrite)
fmt.Println(p) // r--
p = removePerm(p, PermWrite) // idempotent — no change
fmt.Println(p) // r-- (still correct)
fmt.Println(hasPerm(p, PermRead)) // true
fmt.Println(hasPerm(p, PermExecute)) // false
}
Exercise 8 — Skip Zero Value for Security-Sensitive Enum¶
Problem¶
The role system has Admin as the zero value, creating a security vulnerability.
Before¶
package main
import "fmt"
type Role int
const (
Admin Role = iota // 0 — DANGEROUS: zero value = admin
Editor // 1
Viewer // 2
)
func (r Role) String() string {
return [...]string{"Admin", "Editor", "Viewer"}[r]
}
func authorize(r Role, resource string) bool {
return r == Admin
}
func main() {
var r Role // uninitialized — r = 0 = Admin!
fmt.Println("Role:", r)
fmt.Println("Can access /admin:", authorize(r, "/admin")) // true — BUG!
}
Goals¶
- Make
0mean "Unknown/Unauthorized" - Admin should require explicit assignment
var r Roleshould NOT grant admin access
Solution
package main
import "fmt"
type Role int
const (
RoleUnknown Role = 0 // zero value = unauthorized (explicit)
RoleViewer Role = 1
RoleEditor Role = 2
RoleAdmin Role = 3
)
func (r Role) String() string {
switch r {
case RoleUnknown:
return "Unknown"
case RoleViewer:
return "Viewer"
case RoleEditor:
return "Editor"
case RoleAdmin:
return "Admin"
}
return fmt.Sprintf("Role(%d)", int(r))
}
func (r Role) IsValid() bool {
return r >= RoleViewer && r <= RoleAdmin
}
func authorize(r Role, resource string) bool {
if !r.IsValid() {
return false
}
return r == RoleAdmin
}
func main() {
var r Role // r = 0 = RoleUnknown
fmt.Println("Role:", r)
fmt.Println("Can access /admin:", authorize(r, "/admin")) // false — SAFE
admin := RoleAdmin
fmt.Println("Admin can access /admin:", authorize(admin, "/admin")) // true
}
Exercise 9 — Use go generate for String() Automation¶
Problem¶
A large enum has a manually written String() method that is fragile and hard to maintain.
Before¶
package main
import "fmt"
type HTTPStatus int
const (
StatusContinue HTTPStatus = 100
StatusOK HTTPStatus = 200
StatusCreated HTTPStatus = 201
StatusNoContent HTTPStatus = 204
StatusMovedPermanently HTTPStatus = 301
StatusFound HTTPStatus = 302
StatusBadRequest HTTPStatus = 400
StatusUnauthorized HTTPStatus = 401
StatusForbidden HTTPStatus = 403
StatusNotFound HTTPStatus = 404
StatusMethodNotAllowed HTTPStatus = 405
StatusInternalServerError HTTPStatus = 500
StatusBadGateway HTTPStatus = 502
StatusServiceUnavailable HTTPStatus = 503
)
// Manually maintained — easy to miss a status!
func (s HTTPStatus) String() string {
switch s {
case StatusContinue:
return "Continue"
case StatusOK:
return "OK"
case StatusCreated:
return "Created"
case StatusNoContent:
return "No Content"
// ... developer forgot to add the rest!
default:
return fmt.Sprintf("HTTPStatus(%d)", int(s))
}
}
func main() {
fmt.Println(StatusOK)
fmt.Println(StatusNotFound)
fmt.Println(StatusServiceUnavailable) // prints HTTPStatus(503) — missing!
}
Goals¶
- Add a
//go:generate stringerdirective - Remove the manual
String()method - Explain what command to run and what file it generates
Solution
// httpstatus.go
package main
//go:generate stringer -type=HTTPStatus -output=httpstatus_string.go
import "fmt"
type HTTPStatus int
const (
StatusContinue HTTPStatus = 100
StatusOK HTTPStatus = 200
StatusCreated HTTPStatus = 201
StatusNoContent HTTPStatus = 204
StatusMovedPermanently HTTPStatus = 301
StatusFound HTTPStatus = 302
StatusBadRequest HTTPStatus = 400
StatusUnauthorized HTTPStatus = 401
StatusForbidden HTTPStatus = 403
StatusNotFound HTTPStatus = 404
StatusMethodNotAllowed HTTPStatus = 405
StatusInternalServerError HTTPStatus = 500
StatusBadGateway HTTPStatus = 502
StatusServiceUnavailable HTTPStatus = 503
)
// NO manual String() method — generated automatically
func main() {
fmt.Println(StatusOK) // OK
fmt.Println(StatusNotFound) // NotFound
fmt.Println(StatusServiceUnavailable) // ServiceUnavailable
}
Exercise 10 — Use Typed String Constants for HTTP Methods¶
Problem¶
HTTP methods are used as raw string literals throughout the codebase. Any typo (e.g., "GETT") is a runtime bug.
Before¶
package main
import "fmt"
type Route struct {
Method string
Path string
}
func register(method, path string) Route {
return Route{Method: method, Path: path}
}
func main() {
routes := []Route{
register("GET", "/users"),
register("POST", "/users"),
register("GETT", "/orders"), // typo — runtime bug!
register("DELETE", "/users/:id"),
}
for _, r := range routes {
fmt.Printf("%s %s\n", r.Method, r.Path)
}
}
Goals¶
- Define
type HTTPMethod string - Define constants
GET,POST,PUT,DELETE,PATCH registershould acceptHTTPMethod, notstring"GETT"should cause a compile error (or at least be uncreatable as a constant)
Solution
package main
import "fmt"
type HTTPMethod string
const (
GET HTTPMethod = "GET"
POST HTTPMethod = "POST"
PUT HTTPMethod = "PUT"
DELETE HTTPMethod = "DELETE"
PATCH HTTPMethod = "PATCH"
)
type Route struct {
Method HTTPMethod
Path string
}
func register(method HTTPMethod, path string) Route {
return Route{Method: method, Path: path}
}
func main() {
routes := []Route{
register(GET, "/users"),
register(POST, "/users"),
// register("GETT", "/orders"), // compile error: cannot use string as HTTPMethod
register(DELETE, "/users/:id"),
}
for _, r := range routes {
fmt.Printf("%s %s\n", r.Method, r.Path)
}
}
Exercise 11 — Replace Linear Search with Constant-Indexed Table¶
Problem¶
A function uses a linear search through a slice to find a value by enum key. This is O(n) and allocates unnecessarily.
Before¶
package main
import "fmt"
type Priority int
const (
Low Priority = iota
Medium
High
Critical
)
var priorityConfig = []struct {
p Priority
label string
color string
icon string
}{
{Low, "Low", "gray", "▽"},
{Medium, "Medium", "blue", "○"},
{High, "High", "orange", "△"},
{Critical, "Critical", "red", "⚠"},
}
func getLabel(p Priority) string {
for _, cfg := range priorityConfig { // O(n) linear search
if cfg.p == p {
return cfg.label
}
}
return "Unknown"
}
func main() {
fmt.Println(getLabel(High))
fmt.Println(getLabel(Critical))
}
Goals¶
- Replace the slice with constant-indexed arrays (one per field)
- Use
priorityCountsentinel for array sizing - Access becomes O(1) with bounds check
Solution
package main
import "fmt"
type Priority int
const (
Low Priority = iota
Medium
High
Critical
priorityCount // 4
)
// O(1) indexed arrays — compile error if size doesn't match priorityCount
var priorityLabels = [priorityCount]string{"Low", "Medium", "High", "Critical"}
var priorityColors = [priorityCount]string{"gray", "blue", "orange", "red"}
var priorityIcons = [priorityCount]string{"▽", "○", "△", "⚠"}
func (p Priority) IsValid() bool {
return p >= 0 && p < priorityCount
}
func getLabel(p Priority) string {
if !p.IsValid() {
return "Unknown"
}
return priorityLabels[p]
}
func getColor(p Priority) string {
if !p.IsValid() {
return ""
}
return priorityColors[p]
}
func main() {
fmt.Println(getLabel(High))
fmt.Println(getLabel(Critical))
fmt.Println(getColor(Medium))
}
Exercise 12 — Convert Enum to Use Explicit Values for a Public API¶
Problem¶
A package's enum uses iota. A new constant needs to be inserted in the middle. This is a public API that clients may serialize to databases. The insertion will break all stored values.
Before¶
// Package notification — PUBLIC API
package notification
type EventType int
const (
EventCreated EventType = iota // 0
EventUpdated // 1
EventDeleted // 2
// Need to add EventArchived between Updated and Deleted
)
Goals¶
- Convert to explicit integer values
- Insert
EventArchivedbetweenEventUpdatedandEventDeletedsafely - Existing values (Created=0, Updated=1, Deleted=2) must not change
- Add documentation explaining the stability guarantee
- Add
IsValid()method
Solution
// Package notification — PUBLIC API
// EventType values are stable and safe to store in databases.
// New values may be added; do not rely on sequential ordering.
package notification
import "fmt"
// EventType represents the type of a notification event.
// Values are stable across versions and safe to persist.
type EventType int
const (
// EventCreated is fired when a resource is first created.
EventCreated EventType = 0
// EventUpdated is fired when a resource is modified.
EventUpdated EventType = 1
// EventArchived is fired when a resource is moved to the archive.
// Added in v1.2 — does not affect existing stored values.
EventArchived EventType = 2
// EventDeleted is fired when a resource is permanently deleted.
// Note: was 2 in v1.0/v1.1, now 3 after EventArchived was inserted.
// IMPORTANT: if you have stored "2" for EventDeleted in older versions,
// you must run a migration to update those rows to 3.
EventDeleted EventType = 3
)
var eventTypeNames = map[EventType]string{
EventCreated: "created",
EventUpdated: "updated",
EventArchived: "archived",
EventDeleted: "deleted",
}
func (e EventType) String() string {
if name, ok := eventTypeNames[e]; ok {
return name
}
return fmt.Sprintf("EventType(%d)", int(e))
}
func (e EventType) IsValid() bool {
_, ok := eventTypeNames[e]
return ok
}
Summary Table¶
| Exercise | Optimization Type | Key Pattern |
|---|---|---|
| 1 | Magic numbers → named constants | const ( MinAge = 13 ) |
| 2 | Bool flags struct → bitmask | 1 << iota with \| combination |
| 3 | Untyped iota → named type | type Direction int |
| 4 | Manual byte sizes → iota expression | 1 << (10 * iota) |
| 5 | Missing validation → IsValid() | Sentinel + range check |
| 6 | Slice names → compile-time array | [colorCount]string{...} |
| 7 | XOR removal → AND-NOT removal | perm &^ flag |
| 8 | Zero=admin → zero=unknown | iota + 1 or explicit values |
| 9 | Manual String() → go generate | //go:generate stringer |
| 10 | String literals → typed constants | type HTTPMethod string |
| 11 | O(n) search → O(1) array | [priorityCount]string |
| 12 | iota public API → explicit values | Explicit = 0, = 1, etc. |