Skip to content

Boolean — Middle Level

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Glossary
  4. Core Concepts
  5. Real-World Analogies
  6. Mental Models
  7. Pros & Cons
  8. Use Cases
  9. Code Examples
  10. Coding Patterns
  11. Clean Code
  12. Product Use / Feature
  13. Error Handling
  14. Security Considerations
  15. Performance Tips
  16. Metrics & Analytics
  17. Best Practices
  18. Edge Cases & Pitfalls
  19. Common Mistakes
  20. Common Misconceptions
  21. Tricky Points
  22. Test
  23. Tricky Questions
  24. Cheat Sheet
  25. Self-Assessment Checklist
  26. Summary
  27. What You Can Build
  28. Further Reading
  29. Related Topics
  30. Diagrams & Visual Aids
  31. Evolution & Historical Context
  32. Alternative Approaches
  33. Anti-Patterns
  34. Debugging Guide
  35. Comparison with Other Languages

Introduction

Focus: "Why?" and "When to use?"

At the middle level, understanding bool goes beyond "it's true or false." You need to understand when booleans are the right tool, when they become a liability, and how Go's design philosophy around booleans affects system design.

Go's boolean type enforces explicit intent. Unlike C, Python, or JavaScript, Go won't coerce other types into booleans. This forces developers to be precise about what "truthy" means in their domain.

package main

import "fmt"

// Bool used correctly: clear semantic meaning
type Order struct {
    ID          int
    IsPaid      bool
    IsShipped   bool
    IsCancelled bool
}

func (o Order) CanShip() bool {
    return o.IsPaid && !o.IsCancelled && !o.IsShipped
}

func main() {
    order := Order{ID: 42, IsPaid: true}
    fmt.Println("Can ship:", order.CanShip()) // true
}

Understanding when to use bool vs other constructs (enums, iota, state machines) is key at this level.


Prerequisites

  • Solid understanding of boolean basics (Junior level)
  • Go structs and methods
  • Interfaces in Go
  • Error handling patterns
  • Basic testing with testing package

Glossary

Term Definition
Boolean blindness When function parameters are bool values and callers can't tell what they mean
Predicate A function that returns a bool; tests a condition
Short-circuit Evaluation that stops as soon as the result is certain
Flag parameter A boolean argument to a function — generally an anti-pattern
State machine A system where an entity transitions between discrete states (better than multiple booleans)
Bit packing Storing multiple booleans in a single integer using bitwise ops
Boolean accumulator Collecting boolean results over time (e.g., allValid := allValid && check())

Core Concepts

1. Why Go Doesn't Allow Implicit Bool Conversion

In C: if (x) { } works even when x is an int. In Python: if []: evaluates to False.

Go chose strict typing:

var x int = 1
if x { }        // COMPILE ERROR: non-bool x used as bool
if x != 0 { }   // CORRECT: explicit comparison

Why? To eliminate a class of bugs. In C, if (a = b) (assignment, not comparison) compiled silently and was a common bug. Go's explicit requirement forces clarity.

2. Bool in Concurrent Code

Booleans accessed from multiple goroutines need synchronization:

import "sync/atomic"

// WRONG: data race
var isRunning bool

// CORRECT: atomic operation
var isRunning int32 // use 0 and 1

func setRunning(b bool) {
    val := int32(0)
    if b { val = 1 }
    atomic.StoreInt32(&isRunning, val)
}

func getRunning() bool {
    return atomic.LoadInt32(&isRunning) != 0
}

Or use sync.Mutex:

var (
    mu        sync.Mutex
    isRunning bool
)

func setRunning(b bool) {
    mu.Lock()
    defer mu.Unlock()
    isRunning = b
}

3. The ok Idiom

Go's multi-return pattern with bool is idiomatic:

// Map lookup
value, ok := myMap[key]
if !ok {
    // key not found
}

// Type assertion
s, ok := i.(string)
if !ok {
    // not a string
}

// Channel receive
v, ok := <-ch
if !ok {
    // channel closed
}

4. Boolean Accumulation Pattern

func validateAll(user User) bool {
    valid := true
    valid = valid && len(user.Name) > 0
    valid = valid && len(user.Email) > 0
    valid = valid && user.Age >= 18
    return valid
}

// Or using short-circuit:
func validateAll(user User) bool {
    return len(user.Name) > 0 &&
        len(user.Email) > 0 &&
        user.Age >= 18
}

5. Returning Multiple Booleans vs. State

When you have too many booleans, consider a state type:

// Problematic: order state scattered across multiple booleans
type Order struct {
    IsPending   bool
    IsPaid      bool
    IsShipped   bool
    IsDelivered bool
    IsCancelled bool
    IsRefunded  bool
}

// Better: use a state type
type OrderStatus int

const (
    StatusPending   OrderStatus = iota
    StatusPaid
    StatusShipped
    StatusDelivered
    StatusCancelled
    StatusRefunded
)

type Order struct {
    Status OrderStatus
}

Real-World Analogies

Boolean as a light switch vs. a dimmer: For two-state situations, a bool is perfect. But if you need "dimming levels," you need an integer or enum. Choosing bool is choosing a light switch, not a dimmer.

Short-circuit as a loan application: The bank first checks if you have income (cheap check). If you don't, they don't bother checking your credit score (expensive check). hasIncome && goodCreditScore — put the cheap check first.

The ok idiom as a receipt: When you buy something, you get a receipt. value, ok := store.Get(key) — the ok is your receipt confirming the transaction succeeded.


Mental Models

Model 1: Booleans as Flags in a State Machine

Each bool in a struct is a "flag" in a state machine. Too many flags and the state machine becomes unmanageable. Rule of thumb: more than 2-3 boolean flags often means you need an explicit state type.

Model 2: Short-Circuit as Pipeline

A && B && C
[A] → false? → stop, return false
  ↓ true
[B] → false? → stop, return false
  ↓ true
[C] → return C's value

Model 3: The "Semantic Gap"

If you find yourself writing if !isNotInactive, there's a semantic gap. Rename to eliminate double negatives.


Pros & Cons

Pros

  • Explicit, type-safe boolean conditions
  • Short-circuit prevents nil dereferences and division by zero
  • Zero value (false) is the safe default
  • The ok idiom enables clean error-free lookups
  • Atomic boolean operations are straightforward

Cons

  • Boolean blindness: func create(validate, notify, cache bool) — confusing call sites
  • State explosion: 5 booleans = 32 possible states (2^5), hard to reason about
  • No bitwise bool operations: Go booleans don't support &, | (only &&, ||)
  • Concurrency: non-atomic reads/writes cause data races

Use Cases

  1. Feature toggles — runtime feature flags with zero value safety
  2. Validation pipelines — chaining validators with &&
  3. Map/channel existence checks — the ok idiom
  4. Goroutine lifecycledone chan bool or atomic flags
  5. Configuration structstype Config struct { Debug bool; Verbose bool }
  6. ACL / permission systemmap[string]bool for permissions
  7. Circuit breakerisOpen bool to reject requests when a service is down

Code Examples

Example 1: The ok Idiom in Practice

package main

import "fmt"

type Cache struct {
    data map[string]string
}

func NewCache() *Cache {
    return &Cache{data: make(map[string]string)}
}

func (c *Cache) Get(key string) (string, bool) {
    val, ok := c.data[key]
    return val, ok
}

func (c *Cache) Set(key, value string) {
    c.data[key] = value
}

func main() {
    cache := NewCache()
    cache.Set("user:1", "Alice")

    if name, ok := cache.Get("user:1"); ok {
        fmt.Println("Found:", name)
    }

    if _, ok := cache.Get("user:2"); !ok {
        fmt.Println("Not found: user:2")
    }
}

Example 2: Boolean Accumulation with Early Exit

package main

import (
    "fmt"
    "strings"
)

type User struct {
    Name  string
    Email string
    Age   int
}

type ValidationError struct {
    Field   string
    Message string
}

func validateUser(u User) []ValidationError {
    var errs []ValidationError

    if len(u.Name) == 0 {
        errs = append(errs, ValidationError{"Name", "cannot be empty"})
    }
    if !strings.Contains(u.Email, "@") {
        errs = append(errs, ValidationError{"Email", "invalid format"})
    }
    if u.Age < 18 {
        errs = append(errs, ValidationError{"Age", "must be 18 or older"})
    }

    return errs
}

func isValid(u User) bool {
    return len(validateUser(u)) == 0
}

func main() {
    u := User{Name: "Alice", Email: "alice@example.com", Age: 25}
    fmt.Println("Valid:", isValid(u)) // true

    u2 := User{Name: "", Email: "not-an-email", Age: 15}
    errs := validateUser(u2)
    for _, e := range errs {
        fmt.Printf("  %s: %s\n", e.Field, e.Message)
    }
}

Example 3: Goroutine Done Flag

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

type Worker struct {
    running int32 // 0 = stopped, 1 = running
}

func (w *Worker) Start() {
    if !atomic.CompareAndSwapInt32(&w.running, 0, 1) {
        fmt.Println("Already running")
        return
    }
    go func() {
        defer atomic.StoreInt32(&w.running, 0)
        fmt.Println("Worker started")
        time.Sleep(100 * time.Millisecond)
        fmt.Println("Worker done")
    }()
}

func (w *Worker) IsRunning() bool {
    return atomic.LoadInt32(&w.running) == 1
}

func main() {
    w := &Worker{}
    w.Start()
    fmt.Println("Running:", w.IsRunning())
    time.Sleep(200 * time.Millisecond)
    fmt.Println("Running:", w.IsRunning())
}

Example 4: Options Pattern (Avoiding Bool Parameters)

package main

import "fmt"

// Bad: boolean parameter hell
// func CreateUser(name string, isAdmin bool, sendEmail bool, validateEmail bool) {}

// Good: options struct
type CreateUserOptions struct {
    IsAdmin       bool
    SendEmail     bool
    ValidateEmail bool
}

type User struct {
    Name    string
    IsAdmin bool
}

func CreateUser(name string, opts CreateUserOptions) (*User, error) {
    if opts.ValidateEmail {
        // validate...
    }
    u := &User{Name: name, IsAdmin: opts.IsAdmin}
    if opts.SendEmail {
        fmt.Printf("Sending welcome email to %s\n", name)
    }
    return u, nil
}

func main() {
    u, _ := CreateUser("Alice", CreateUserOptions{
        IsAdmin:       true,
        SendEmail:     true,
        ValidateEmail: false,
    })
    fmt.Printf("Created: %+v\n", u)
}

Example 5: Circuit Breaker with Bool

package main

import (
    "fmt"
    "time"
)

type CircuitBreaker struct {
    isOpen     bool
    failCount  int
    threshold  int
    resetAfter time.Duration
    openedAt   time.Time
}

func NewCircuitBreaker(threshold int, resetAfter time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        threshold:  threshold,
        resetAfter: resetAfter,
    }
}

func (cb *CircuitBreaker) CanExecute() bool {
    if !cb.isOpen {
        return true
    }
    // Check if reset time has passed
    if time.Since(cb.openedAt) > cb.resetAfter {
        cb.isOpen = false
        cb.failCount = 0
        return true
    }
    return false
}

func (cb *CircuitBreaker) RecordFailure() {
    cb.failCount++
    if cb.failCount >= cb.threshold {
        cb.isOpen = true
        cb.openedAt = time.Now()
        fmt.Println("Circuit breaker OPEN")
    }
}

func main() {
    cb := NewCircuitBreaker(3, 5*time.Second)

    for i := 0; i < 5; i++ {
        if cb.CanExecute() {
            fmt.Printf("Call %d: executing\n", i+1)
            cb.RecordFailure() // simulate failure
        } else {
            fmt.Printf("Call %d: rejected (circuit open)\n", i+1)
        }
    }
}

Coding Patterns

Pattern 1: Functional Options with Bool

type ServerConfig struct {
    TLS     bool
    Logging bool
    Debug   bool
}

type Option func(*ServerConfig)

func WithTLS() Option       { return func(c *ServerConfig) { c.TLS = true } }
func WithLogging() Option   { return func(c *ServerConfig) { c.Logging = true } }
func WithDebug() Option     { return func(c *ServerConfig) { c.Debug = true } }

func NewServer(opts ...Option) *ServerConfig {
    c := &ServerConfig{} // all false by default
    for _, o := range opts {
        o(c)
    }
    return c
}

Pattern 2: Lazy Boolean with Function

// Expensive computation only when needed
type LazyBool struct {
    computed bool
    value    bool
    compute  func() bool
}

func (l *LazyBool) Get() bool {
    if !l.computed {
        l.value = l.compute()
        l.computed = true
    }
    return l.value
}

Pattern 3: All/Any Helpers

func All(predicates ...bool) bool {
    for _, p := range predicates {
        if !p {
            return false
        }
    }
    return true
}

func Any(predicates ...bool) bool {
    for _, p := range predicates {
        if p {
            return true
        }
    }
    return false
}

Clean Code

Avoid boolean parameters (boolean blindness):

// Hard to read
processOrder(order, true, false, true)

// Clear
processOrder(order, ProcessOptions{
    Validate: true,
    Notify:   false,
    Archive:  true,
})

Extract complex boolean logic:

// Hard to read
if user.Age >= 18 && user.Country == "US" && !user.IsBanned && user.SubscriptionLevel > 0 {
}

// Clear
if canAccessPremiumContent(user) {
}

func canAccessPremiumContent(u User) bool {
    return u.Age >= 18 &&
        u.Country == "US" &&
        !u.IsBanned &&
        u.SubscriptionLevel > 0
}


Product Use / Feature

Real-World Feature Flag System:

Companies like LinkedIn, Etsy, and Flickr pioneered feature flags. Modern systems like LaunchDarkly serve billions of bool evaluations per day.

type FeatureFlag struct {
    Name        string
    Enabled     bool
    RolloutPct  float64  // 0.0 to 1.0
    AllowedUsers map[string]bool
}

type FlagService struct {
    flags map[string]FeatureFlag
}

func (fs *FlagService) IsEnabled(flagName, userID string) bool {
    flag, ok := fs.flags[flagName]
    if !ok {
        return false // safe default
    }
    if !flag.Enabled {
        return false
    }
    // Per-user override
    if flag.AllowedUsers[userID] {
        return true
    }
    // Percentage rollout using hash
    // (simplified: use actual hash of userID in production)
    return flag.RolloutPct >= 1.0
}

Error Handling

package main

import (
    "errors"
    "fmt"
)

var (
    ErrNotFound   = errors.New("not found")
    ErrPermission = errors.New("permission denied")
)

type Result struct {
    Value   interface{}
    Success bool
    Error   error
}

func fetchUser(id int) Result {
    if id <= 0 {
        return Result{Success: false, Error: ErrNotFound}
    }
    return Result{Value: "Alice", Success: true}
}

func main() {
    r := fetchUser(1)
    if !r.Success {
        fmt.Println("Error:", r.Error)
        return
    }
    fmt.Println("User:", r.Value)
}

Security Considerations

Boolean as Access Control — Dangerous Pattern

// DANGEROUS: single bool controls admin access
type Session struct {
    UserID  int
    IsAdmin bool // If JWT is tampered or session is hijacked, attacker gains admin
}

// BETTER: check permissions at the database/service level
type Session struct {
    UserID string
    Token  string
}

func (s Session) HasPermission(perm string) bool {
    // Fetch from DB or token claims, don't store in session
    return permissionService.Check(s.UserID, perm)
}

Timing Attacks with Bool

// VULNERABLE: timing attack possible (early return leaks information)
func checkPassword(input, stored string) bool {
    if len(input) != len(stored) {
        return false  // faster return leaks length info
    }
    for i := range input {
        if input[i] != stored[i] {
            return false  // early return leaks position info
        }
    }
    return true
}

// SAFE: constant-time comparison
import "crypto/subtle"

func checkPassword(input, stored []byte) bool {
    return subtle.ConstantTimeCompare(input, stored) == 1
}

Performance Tips

Put Cheapest Check First in &&

// Slow: always calls len() AND regex, even for empty strings
if regexp.MustCompile(`^[a-z]+$`).MatchString(s) && len(s) > 0 {
}

// Fast: len() is O(1), regex is expensive
if len(s) > 0 && regexp.MustCompile(`^[a-z]+$`).MatchString(s) {
}

map[string]struct{} vs map[string]bool for Sets

// map[string]bool: 1 byte per entry for value
visited := make(map[string]bool)
visited["page1"] = true

// map[string]struct{}: 0 bytes per entry for value (empty struct)
visited := make(map[string]struct{})
visited["page1"] = struct{}{}
_, exists := visited["page1"]

For large sets (thousands of entries), struct{} saves memory.


Metrics & Analytics

type ABTestResult struct {
    Variant    string
    Converted  bool
    ClickedCTA bool
    Bounced    bool
}

func ConversionRate(results []ABTestResult) float64 {
    if len(results) == 0 {
        return 0
    }
    converted := 0
    for _, r := range results {
        if r.Converted {
            converted++
        }
    }
    return float64(converted) / float64(len(results))
}

Best Practices

  1. Boolean blindness: Use options structs instead of bool params
  2. Prefer positive naming: isEnabled over isNotDisabled
  3. Extract complex boolean expressions into named functions
  4. Use ok idiom for map lookups and type assertions
  5. Concurrency: use sync/atomic or sync.Mutex for shared booleans
  6. State machines beat boolean explosion (>3 related booleans)
  7. Short-circuit order: cheapest checks first, side-effectful checks last
  8. Default to false: design structs so false is safe

Edge Cases & Pitfalls

Pitfall 1: State Explosion

// 4 booleans = 16 possible states
// Many are likely invalid/impossible
type Package struct {
    IsPacked   bool
    IsShipped  bool
    IsDelivered bool
    IsReturned bool
}
// Can be IsPacked=false AND IsDelivered=true? That's invalid!
// Use a state type instead.

Pitfall 2: Race Condition

var done bool

go func() {
    time.Sleep(100 * time.Millisecond)
    done = true // DATA RACE: unsynchronized write
}()

for !done { // DATA RACE: unsynchronized read
    time.Sleep(10 * time.Millisecond)
}

Fix with channel or atomic.

Pitfall 3: Shadowed ok

ok := true
if val, ok := someMap[key]; ok { // 'ok' here is a NEW variable
    _ = val
}
// outer 'ok' is still true, not affected

Common Mistakes

1. Not Using Short-Circuit for Nil Safety

// PANIC if ptr is nil
if ptr.field > 0 && ptr != nil { }

// SAFE: check nil first
if ptr != nil && ptr.field > 0 { }

2. Boolean Struct Field with Misleading Zero Value

type Config struct {
    EnableCache bool // zero value = false = cache disabled
    // But what if enabling cache is the desired default?
}
// Better: make "enabled" the explicit case, or use a pointer

3. Using Bool as Bitfield (C-style)

// Don't try to pack booleans manually in Go
flags := 0
flags |= 1 << 0 // bit 0 = isActive
flags |= 1 << 1 // bit 1 = isPaid
// This is error-prone and unreadable. Use a struct with bool fields.

Common Misconceptions

"Short-circuit means only one side is evaluated": Both sides can be evaluated; short-circuit means evaluation stops as soon as the result is determined. In false && expensive(), expensive() is not called. In true && expensive(), it is.

"bool is thread-safe": No. Concurrent reads and writes to a bool are a data race without synchronization.

"You can convert bool to int in Go": You cannot with a simple cast. You need explicit logic: if b { return 1 }; return 0.


Tricky Points

Operator Precedence (Full Table)

Unary:   !
Binary:  * / % << >> & &^
         + - | ^
         == != < <= > >=
         &&
         ||

So a || b && c = a || (b && c)&& binds tighter.

Boolean Short-Circuit with Side Effects

count := 0
inc := func() bool { count++; return count < 3 }

// count will be incremented until false
for inc() && inc() {
}
fmt.Println(count) // depends on short-circuit behavior

Demorgan's Laws (Useful for Simplification)

!(A && B) == !A || !B
!(A || B) == !A && !B
// These are equivalent
if !(isLoggedIn && isAdmin) { }
if !isLoggedIn || !isAdmin { }

Test

package bool_test

import (
    "testing"
)

func TestAll(t *testing.T) {
    tests := []struct {
        name     string
        args     []bool
        expected bool
    }{
        {"all true", []bool{true, true, true}, true},
        {"one false", []bool{true, false, true}, false},
        {"empty", []bool{}, true}, // vacuous truth
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := All(tt.args...); got != tt.expected {
                t.Errorf("All(%v) = %v, want %v", tt.args, got, tt.expected)
            }
        })
    }
}

func All(preds ...bool) bool {
    for _, p := range preds {
        if !p { return false }
    }
    return true
}

func TestShortCircuit(t *testing.T) {
    called := false
    sideEffect := func() bool {
        called = true
        return true
    }

    _ = false && sideEffect()
    if called {
        t.Error("sideEffect should not have been called with short-circuit")
    }
}

Tricky Questions

Q1: What is false && sideEffect() guaranteed to do? A: sideEffect() is guaranteed NOT to be called. Short-circuit evaluation is defined behavior in Go.

Q2: Are bool variables safe to share across goroutines? A: No. They are not atomic. Use sync/atomic with int32 or sync.Mutex.

Q3: What does !(a && b) equal? A: !a || !b — De Morgan's law.

Q4: When should you use a state type instead of multiple booleans? A: When you have 3+ related boolean fields that represent mutually exclusive states.

Q5: What's wrong with func Process(data string, validate bool, cache bool)? A: Boolean blindness — callers write Process(data, true, false) which is unreadable. Use an options struct.


Cheat Sheet

Type:          bool
Values:        true / false
Zero value:    false (safe default)
Size:          1 byte

Idiomatic patterns:
  val, ok := map[key]        // map lookup
  val, ok := iface.(Type)    // type assertion
  v, ok := <-ch              // channel receive

Concurrency:
  var flag int32             // NOT bool
  atomic.StoreInt32(&flag, 1)
  atomic.LoadInt32(&flag)

Anti-patterns:
  func f(a, b, c bool)       // use options struct
  !(a && b)                  // use !a || !b (clearer)
  multiple booleans for state // use state type/iota

De Morgan's:
  !(A && B) == !A || !B
  !(A || B) == !A && !B

Self-Assessment Checklist

  • I understand why Go forbids implicit bool conversions
  • I use the ok idiom for map lookups and type assertions
  • I know when to use options structs instead of bool parameters
  • I understand state explosion and when to use iota instead
  • I know booleans are NOT thread-safe
  • I can apply De Morgan's laws to simplify logic
  • I put cheaper checks before expensive ones in &&/||
  • I understand map[string]struct{} vs map[string]bool for sets

Summary

At the middle level, bool is about design decisions: - When is bool the right type vs. an enum/state type? - Boolean blindness is a real code smell — avoid bool parameters - Concurrency requires atomic or mutex-protected access - Short-circuit is not just a trick — it's a design tool - State explosion is a warning sign that you need a different abstraction

Well-designed boolean usage leads to more readable, safer, and more maintainable code.


What You Can Build

  • Feature flag service with per-user and percentage rollouts
  • Validation pipeline returning detailed errors alongside bool results
  • Circuit breaker using atomic bool to protect external services
  • Permission system with map[string]bool per user
  • A/B testing framework with bool-based cohort assignment
  • Configuration loader with functional options pattern

Further Reading


  • iota and constants — for multi-state enumeration
  • sync/atomic — thread-safe bool-like operations
  • channels — idiomatic Go signaling (done chan struct{})
  • interfaces — type assertions use the ok idiom
  • error handlingval, err vs val, bool patterns

Diagrams & Visual Aids

State Machine vs. Multiple Booleans

Multiple Booleans (messy):
  IsPending  | IsPaid | IsShipped | IsDelivered
  -----------|--------|-----------|------------
  true       | false  | false     | false       ← valid
  false      | true   | false     | false       ← valid
  false      | true   | true      | false       ← valid
  true       | true   | true      | false       ← INVALID! (paid AND pending?)
  ...32 combinations total, many invalid

State Machine (clean):
  Pending → Paid → Shipped → Delivered
         ↘ Cancelled

Short-Circuit Decision Tree

A && B:
  Evaluate A
    A = false → Result = false (skip B)
    A = true  → Evaluate B → Result = B

A || B:
  Evaluate A
    A = true  → Result = true (skip B)
    A = false → Evaluate B → Result = B

Evolution & Historical Context

The bool type as a distinct type was introduced by C++ (C used int). Java had boolean. Go's strict no-implicit-conversion rule was a deliberate design choice to avoid C's notorious if (x = 0) bug family.

Go's ok idiom (returning (T, bool)) was inspired by the need to distinguish "key not found" from "key found with zero value" in maps — a problem that C++ solved awkwardly with map::find().


Alternative Approaches

Approach When to Use
bool field Simple two-state: enabled/disabled
iota enum 3+ mutually exclusive states
map[string]bool Dynamic set of permissions/features
chan struct{} Goroutine signaling
sync/atomic int32 Thread-safe bool flag
Bitmask Many flags, memory-critical systems

Anti-Patterns

  1. Boolean parameterfunc create(validate bool) — use options struct
  2. Multiple bool fields for related state — use iota
  3. Unsynchronized bool in goroutines — use atomic
  4. Double negation!isNotActive — rename
  5. Returning bool instead of error — for functions that can fail, return error
  6. if flag == true — redundant comparison

Debugging Guide

Problem: Bool never changes in goroutine

// PROBLEM: compiler may cache the value in a register
done := false
go func() { done = true }()
for !done { } // may loop forever
// FIX: use channel or atomic

Problem: Short-circuit masking bug

// If validate() has a side effect and is being skipped
if len(items) > 0 && validate(items) {
}
// DEBUG: temporarily remove short-circuit
valid := validate(items) // force evaluation
if len(items) > 0 && valid {
}

Problem: Zero value confusion

type Config struct {
    DisableCache bool // zero value = false = cache enabled (confusing!)
}
// FIX: rename to EnableCache (zero value = false = cache disabled)


Comparison with Other Languages

Language if 1 { } Null/nil as false Bool size Atomic bool
Go No (error) No 1 byte No native (use int32)
C Yes Yes 1 byte _Atomic bool
Python Yes Yes (None=False) ~28 bytes threading.Event
Java No No (NPE) 1 byte AtomicBoolean
Rust No No 1 byte AtomicBool
JavaScript Yes Yes varies N/A (single-threaded)

Go is unique in having no atomic bool — idiomatic Go uses channels or int32 with sync/atomic.