Mastering Go's Core Data Types: Integers, Floats, Booleans, and Strings
Lukas Schneider
DevOps Engineer · Leapcell

Go, known for its simplicity, efficiency, and strong typing, relies heavily on its fundamental data types to build robust applications. Unlike dynamically typed languages, Go requires you to explicitly define the type of a variable, which helps in catching errors early and improving code readability. This article delves into Go's four primary built-in data types: integers, floating-point numbers, booleans, and strings, providing a comprehensive understanding of their characteristics and usage with practical code examples.
Integers: The Bedrock of Counting
Integers in Go represent whole numbers, both positive and negative, without any fractional components. Go provides a variety of integer types, differing mainly in their size (number of bits) and whether they are signed or unsigned. Choosing the right integer type is crucial for memory efficiency and preventing overflow errors.
Signed Integers: Can represent both positive and negative values.
int8
: -128 to 127int16
: -32768 to 32767int32
: -2,147,483,648 to 2,147,483,647 (also aliased asrune
for Unicode code points)int64
: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807int
: A platform-dependent signed integer type. On 32-bit systems, it's typically 32-bit; on 64-bit systems, it's 64-bit. This is the most commonly used integer type when the exact size is not critical.
Unsigned Integers: Can only represent non-negative values (zero and positive). This allows them to store larger positive numbers within the same bit size compared to their signed counterparts.
uint8
: 0 to 255 (also aliased asbyte
for raw data)uint16
: 0 to 65535uint32
: 0 to 4,294,967,295uint64
: 0 to 18,446,744,073,709,551,615uint
: A platform-dependent unsigned integer type, mirroring the size ofint
.uintptr
: An unsigned integer type large enough to store the uninterpreted bits of a pointer value. Rarely used directly in application code.
Integer Declaration and Initialization:
package main import "fmt" func main() { // Explicit type declaration var age int = 30 var population int64 = 8_000_000_000 // Underscores for readability (Go 1.13+) var score uint8 = 250 // Max value for uint8 is 255 var temperature int = -5 // Short variable declaration (type inferred) count := 100 id := uint(12345) fmt.Printf("Age: %d (Type: %T)\n", age, age) fmt.Printf("Population: %d (Type: %T)\n", population, population) fmt.Printf("Score: %d (Type: %T)\n", score, score) fmt.Printf("Temperature: %d (Type: %T)\n", temperature, temperature) fmt.Printf("Count: %d (Type: %T)\n", count, count) fmt.Printf("ID: %d (Type: %T)\n", id, id) // Integer overflow example (will cause a compile-time error if value exceeds type range) // var tooBig uint8 = 300 // Compile-time error: constant 300 overflows uint8 }
When performing arithmetic operations, Go requires that operands have the same type. Explicit type conversion is necessary if types differ.
package main import "fmt" func main() { var a int = 10 var b int64 = 20 // c := a + b // Compile-time error: mismatched types int and int64 // Correct way: Type conversion c := int64(a) + b d := a + int(b) fmt.Printf("Result c: %d (Type: %T)\n", c, c) fmt.Printf("Result d: %d (Type: %T)\n", d, d) }
Floating-Point Numbers: Handling Decimals
Floating-point numbers in Go are used to represent numbers with fractional parts. They are essential for calculations involving precision, such as scientific computations, financial applications, and graphics. Go provides two floating-point types:
float32
: A single-precision floating-point number, typically 32-bit. Offers less precision but uses less memory.float64
: A double-precision floating-point number, typically 64-bit. Offers higher precision and is the default type inferred for floating-point literals.
Floating-Point Declaration and Initialization:
package main import "fmt" func main() { var pi float32 = 3.14159 var gravity float64 = 9.80665 var price = 19.99 // Inferred as float64 fmt.Printf("Pi: %f (Type: %T)\n", pi, pi) fmt.Printf("Gravity: %f (Type: %T)\n", gravity, gravity) fmt.Printf("Price: %f (Type: %T)\n", price, price) // Floating-point arithmetic result := pi * float32(gravity) // Explicit conversion to perform operation fmt.Printf("Result of multiplication: %f\n", result) // Due to the nature of floating-point representation, // direct equality comparison can sometimes be problematic. x := 0.1 y := 0.2 z := 0.3 fmt.Println("x + y == z?", (x+y == z)) // Will likely print false due to precision issues fmt.Printf("x + y = %.17f\n", x+y) fmt.Printf("z = %.17f\n", z) // Prefer comparing with a small epsilon for equality checks epsilon := 0.00000001 if (x+y)-z < epsilon && (x+y)-z > -epsilon { fmt.Println("x + y is approximately equal to z") } }
Booleans: The Logic Gates
Booleans represent truth values and are fundamental for control flow in programming. A boolean variable can hold one of two predefined values: true
or false
.
Boolean Declaration and Initialization:
package main import "fmt" func main() { var isActive bool = true var isLoggedIn = false // Inferred as bool fmt.Printf("Is Active: %t (Type: %T)\n", isActive, isActive) fmt.Printf("Is Logged In: %t (Type: %T)\n", isLoggedIn, isLoggedIn) // Conditional statements using booleans if isActive && !isLoggedIn { fmt.Println("User is active but not logged in.") } result := 10 > 5 // true isEligible := age >= 18 // Assuming 'age' is defined fmt.Printf("10 > 5 is: %t\n", result) // fmt.Printf("Is Eligible: %t\n", isEligible) uncomment if 'age' is defined }
Booleans are extensively used in if
, else if
, else
statements, for
loop conditions, and logical operations (&&
for AND, ||
for OR, !
for NOT).
Strings: Handling Textual Data
Strings in Go represent sequences of immutable Unicode characters (runes). They are a built-in type and are exceptionally efficient due to their immutable nature, which allows for safe sharing and optimized memory management.
String Declaration and Initialization:
package main import "fmt" func main() { // Double quotes for interpreted string literals var message string = "Hello, Go!" name := "Alice" greeting := "こんにちは世界" // Unicode characters are perfectly fine fmt.Printf("Message: %s (Type: %T)\n", message, message) fmt.Printf("Name: %s (Type: %T)\n", name, name) fmt.Printf("Greeting: %s (Type: %T)\n", greeting, greeting) // Raw string literals (backticks `): interpret content literally, no escape sequences multiLineString := `This is a multi-line string. Newlines and special characters like \t are treated as literal.` fmt.Println(multiLineString) // String concatenation fullName := name + " Smith" fmt.Println("Full Name:", fullName) // String length (number of bytes) fmt.Printf("Length of 'message': %d bytes\n", len(message)) fmt.Printf("Length of 'greeting': %d bytes (not runes!)\n", len(greeting)) // Iterating over a string (by runes) fmt.Println("Iterating over 'greeting':") for i, r := range greeting { fmt.Printf("Index: %d, Rune: %c (Unicode Value: %U)\n", i, r, r) } // Accessing characters by index (byte-wise) fmt.Printf("First byte of 'message': %c\n", message[0]) // Prints 'H' // fmt.Printf("First byte of 'greeting': %c\n", greeting[0]) // Might print for multi-byte runes // Accessing runes directly requires care // Strings are immutable: // message[0] = 'h' // Compile-time error: cannot assign to message[0] (value of type byte) // Substring (slicing) part := message[0:5] // "Hello" fmt.Println("Sliced part:", part) // String comparison s1 := "apple" s2 := "banana" s3 := "apple" fmt.Printf("'apple' == 'banana': %t\n", s1 == s2) fmt.Printf("'apple' == 'apple': %t\n", s1 == s3) }
Go strings are UTF-8 encoded. While len()
returns the number of bytes, iterating with for range
correctly decodes UTF-8 and provides individual Unicode rune
(int32) values along with their starting byte index. This distinction is crucial when working with multicultural text.
Conclusion
Understanding Go's basic data types—integers, floating-point numbers, booleans, and strings—is the cornerstone of writing effective Go programs. Each type serves a specific purpose, and Go's strict typing ensures type safety and predictable behavior. By judiciously selecting the appropriate type, developers can create robust, efficient, and readable code. Mastering these fundamentals is the first step towards leveraging Go's power for a wide range of applications, from system programming to web services. As you progress, you'll find these basic types forming the building blocks for more complex data structures like arrays, slices, maps, and structs, which further empower your Go development journey.