Boolean Type
Understand Go's bool type — its two possible values, default zero value, logical operators, strict type rules, and how to avoid the most common mistakes when working with booleans.
What Is a Boolean Type?
A boolean type holds exactly one of two values: true or false. In Go, that type is named bool. It is the simplest possible data type — a single bit of information that answers a yes/no question.
Think of a bool as a light switch. The switch can be in only two states: on (true) or off (false). There is no "maybe", no number that substitutes for true, and no way to treat a string like "true" as the boolean value itself. This absolute separation is a deliberate design choice that eliminates whole categories of bugs found in languages where truthiness is fuzzy.
A bool is a predeclared type — the language defines it automatically, so you never import anything to use it. It is not a keyword (you cannot write bool as a variable name without trouble, but it is technically possible to shadow it — something you should never do intentionally).
Declaring and Using Bools
You can declare a boolean variable with an explicit type, let the compiler infer it, or use a short variable declaration. All of these are common and correct.
package main
import "fmt"
func main() {
var isReady bool = true // explicit type and initialization
var isDone = false // type inferred as bool
isActive := true // short declaration inside a function
fmt.Println(isReady, isDone, isActive)
}
This prints true false true. Each variable is a bool that holds one of the two legal values.
When you declare a bool with var but do not assign a value, it gets its zero value. Every Go type has one, and for bool that default is false.
var flag bool
fmt.Println(flag) // prints false
There is no way to declare a bool that starts as true without explicitly setting it.
Bool Is a Predeclared Identifier:
The name bool is a predeclared type, similar to int or string. You can technically create a local variable named bool, which would shadow the type and make your code unreadable. Avoid this. If you see code that uses bool as a variable name, treat it as a bug.
The Zero Value of Bool
Every variable declared without an explicit initial value gets the zero value of its type. For bool, that zero value is false. This is consistent: a boolean that hasn't been set to anything meaningful is assumed to be "off" rather than "on".
var fileExists bool
fmt.Println("File exists:", fileExists) // File exists: false
This behavior is useful for flags that track whether something has happened. You can declare a flag, do nothing, and safely check it — it starts as false. There is no risk of an uninitialized boolean containing garbage memory.
Zero Value = Predictable Default:
If your program prints false for an uninitialized bool, everything is working correctly. That is the expected and guaranteed behavior. You never need to write var x bool = false if you only want the default — the compiler gives you that for free.
Logical Operators — Combining and Negating Bools
Go provides three logical operators that work exclusively on bool values:
&&(logical AND) — returnstrueonly if both operands aretrue||(logical OR) — returnstrueif at least one operand istrue!(logical NOT) — flips the boolean:!trueisfalse,!falseistrue
These operators only accept bool operands. You cannot write 1 && 0 or "nonempty" || false. The compiler rejects anything that is not exactly of type bool.
a := true
b := false
fmt.Println(a && b) // false — both must be true
fmt.Println(a || b) // true — one is enough
fmt.Println(!a) // false — negation of true
fmt.Println(!b) // true — negation of false
Both && and || use short-circuit evaluation. That means the right-hand operand is evaluated only if the left-hand operand does not already determine the result:
- For
&&, if the left side isfalse, the whole expression isfalseno matter what the right side is, so the right side is skipped. - For
||, if the left side istrue, the whole expression istrue, and the right side is skipped.
This is not just an optimization — it can matter when the right side involves a function call or an expression with side effects.
func expensiveCheck() bool {
fmt.Println("expensiveCheck ran")
return true
}
func main() {
false && expensiveCheck() // expensiveCheck never runs
true || expensiveCheck() // expensiveCheck never runs
true && expensiveCheck() // expensiveCheck runs, prints message
}
Short-Circuit Hides Side Effects:
If you place a function call with important side effects on the right side of && or ||, that call may never execute. This is a common source of subtle bugs when someone refactors logical conditions. Always be aware which side will be evaluated and when.
Comparison Operators Produce Bools
The operators ==, !=, <, >, <=, and >= compare two values and return a bool. They work on numeric types, strings, and any type that supports ordering or equality. The result is always true or false.
x := 10
y := 20
isEqual := x == y // false
isLess := x < y // true
name1 := "Go"
name2 := "go"
sameName := name1 == name2 // false — strings are compared bytewise
Comparison is how bools are born in most real programs. You rarely write var flag = true by hand; more often, a comparison produces the boolean you store or pass along.
Boolean Expressions Feel Natural:
If you see 10 \u003c 20 in Go code and read it as "10 is less than 20", the result is exactly the true you expect. This directness makes conditionals easy to reason about — there is no hidden conversion from numbers to truth values.
Bool Is Strictly a Separate Type
Go does not convert booleans to any other type, and no other type converts to bool automatically. There is no concept of "truthiness." The expression inside an if statement, a for condition, or a logical operator must be of type bool. Anything else is a compile-time error.
// This will NOT compile
var count = 5
if count { // invalid operation: count (variable of type int) is not bool
fmt.Println("count is nonzero")
}
You must write an explicit comparison:
if count != 0 {
fmt.Println("count is nonzero")
}
Similarly, you cannot assign an integer to a bool, nor a bool to an integer:
var flag bool = 1 // compile error: cannot use 1 (untyped int constant) as bool
var n int = true // compile error: cannot use true (untyped bool constant) as int
The language forces you to be explicit about what you mean. This eliminates the silent bugs that arise in languages where any nonzero number or non-empty string counts as "true."
Forgetting the Explicit Comparison Breaks Compilation:
Writing if x { ... } where x is an integer, string, pointer, or any non-bool type will cause a compile error. This is not a warning — the program will not build. The fix is always to add a comparison: if x != 0, if s != "", if ptr != nil, etc.
Common Mistakes with Booleans
Beyond the type strictness covered above, there are patterns that trip up developers new to Go.
Accidentally shadowing a boolean variable inside a block is easy to do. Because := declares a new variable, you might create a new local bool that hides the outer one, leaving the outer variable unchanged.
var success bool
if someCondition {
success := doSomething() // this success is a NEW variable, not the outer one
// outer success remains false
}
// success here is still false
The fix is to use = instead of := when you intend to reassign an existing variable, or to declare the variable outside and avoid redeclaration.
Overcomplicating boolean expressions is another common issue. Beginners sometimes write if flag == true or if flag != false when if flag or if !flag is clearer. The Go compiler does not penalize the verbose form, but it signals a misunderstanding that flag itself already answers the question.
Misunderstanding short-circuit behavior with function calls was already noted. The warning is worth repeating: never put a function call with a required side effect on the right side of && or || and rely on it always executing.
Practical Usage Patterns
Bools show up everywhere in Go code. The most frequent patterns include:
- Conditional flags: tracking whether a feature is enabled, a file is open, or a connection is established.
- Guard clauses: early returns that check a boolean condition and exit a function immediately if something isn't right.
- Return values from predicate functions: functions whose entire job is to answer a yes/no question — for example,
strings.Contains(s, substr) bool,os.IsNotExist(err) bool, or custom validators. - Loop conditions:
forloops use a boolean expression to decide whether to continue iterating.
Here is a realistic function that demonstrates several of these patterns together:
package main
import (
"fmt"
"os"
)
func fileIsReadable(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
// Check that it is a regular file and the permission bits allow reading
return info.Mode().IsRegular() && info.Mode().Perm()&0444 != 0
}
func main() {
path := "example.txt"
if fileIsReadable(path) {
fmt.Println(path, "is readable")
} else {
fmt.Println(path, "cannot be read or does not exist")
}
}
The function fileIsReadable is a predicate — it returns a bool that directly answers a question. The caller uses that result in an if statement without wrapping it in an extra comparison. Inside the function, err != nil produces a bool that triggers an early return. The final expression chains logical && to combine two boolean checks into one final answer.
This is idiomatic Go: simple, explicit, and unmistakable about what each condition means.
Summary
The boolean type in Go is intentionally small and strict. It admits only true and false, has a zero value of false, and refuses to silently convert to or from any other type. Every if statement, every loop condition, and every logical operator demands a genuine bool — and the compiler enforces that rule without exception.
The single most important takeaway is that Go treats the boolean as a first-class type that cannot be faked. You never guess what "truthy" means, you never count on automatic conversion, and you never debug a nil pointer because an empty string accidentally evaluated to false. Every condition in your program is spelled out.