Skip to main content

Basic Types

Go's Type System​

Go is a statically typed language. Every variable's type is determined at compile time, and there are no implicit type conversions. To operate on values of different types, you must explicitly convert them.

This philosophy catches type-related bugs at compile time, dramatically reducing runtime errors.


Integer Types​

Signed Integers​

TypeSizeRange
int81 byte-128 to 127
int162 bytes-32,768 to 32,767
int324 bytes-2,147,483,648 to 2,147,483,647
int648 bytes-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
intPlatform dependent32-bit system: int32, 64-bit system: int64

Unsigned Integers​

TypeSizeRange
uint81 byte0 to 255
uint162 bytes0 to 65,535
uint324 bytes0 to 4,294,967,295
uint648 bytes0 to 18,446,744,073,709,551,615
uintPlatform dependent32-bit system: uint32, 64-bit system: uint64
uintptrPlatform dependentLarge enough to hold a pointer
package main

import (
"fmt"
"math"
)

func main() {
// Print the maximum value of each type
fmt.Println("int8 max:", math.MaxInt8) // 127
fmt.Println("int16 max:", math.MaxInt16) // 32767
fmt.Println("int32 max:", math.MaxInt32) // 2147483647
fmt.Println("int64 max:", math.MaxInt64) // 9223372036854775807

fmt.Println("uint8 max:", math.MaxUint8) // 255
fmt.Println("uint16 max:", math.MaxUint16) // 65535
fmt.Println("uint32 max:", math.MaxUint32) // 4294967295

// Practical usage
var counter int = 0
var fileSize int64 = 1_234_567_890
var flags uint8 = 0b11001010
var port uint16 = 8080

fmt.Println(counter, fileSize, flags, port)
}

int vs int64 β€” Platform-Dependent Size​

The size of int depends on the platform (OS and CPU architecture). On modern 64-bit systems it is 8 bytes, but if you need a guaranteed size for cross-platform code, use int64.

package main

import (
"fmt"
"unsafe"
)

func main() {
var i int
var i64 int64

fmt.Printf("int size: %d bytes\n", unsafe.Sizeof(i)) // 64-bit: 8
fmt.Printf("int64 size: %d bytes\n", unsafe.Sizeof(i64)) // always 8

// General counters and indices β€” use int (convention)
for i := 0; i < 10; i++ {
_ = i
}

// File sizes, timestamps β€” use int64 (guaranteed size)
var timestamp int64 = 1_700_000_000
var fileOffset int64 = 4_294_967_296 // over 4 GB
fmt.Println(timestamp, fileOffset)
}

Floating-Point Types​

TypeSizePrecision
float324 bytes~7 decimal digits
float648 bytes~15–16 decimal digits

The default type for floating-point literals in Go is float64.

package main

import (
"fmt"
"math"
)

func main() {
var f32 float32 = 3.14159265358979
var f64 float64 = 3.14159265358979

// float32 loses precision
fmt.Printf("float32: %.10f\n", f32) // 3.1415927410
fmt.Printf("float64: %.10f\n", f64) // 3.1415926536

// Mathematical constants
fmt.Println("Ο€:", math.Pi)
fmt.Println("e:", math.E)
fmt.Println("max float64:", math.MaxFloat64)

// Special values
inf := math.Inf(1) // positive infinity
nan := math.NaN() // NaN (Not a Number)
fmt.Println("inf:", inf)
fmt.Println("nan:", nan)
fmt.Println("IsNaN:", math.IsNaN(nan))
fmt.Println("IsInf:", math.IsInf(inf, 1))
}

Complex Types​

TypeSizeComposition
complex648 bytesfloat32 real + float32 imaginary
complex12816 bytesfloat64 real + float64 imaginary
package main

import (
"fmt"
"math/cmplx"
)

func main() {
// Complex number literals
c1 := 3 + 4i // complex128 (default)
c2 := complex(1, 2) // complex(real, imaginary)

fmt.Println("c1:", c1)
fmt.Println("c2:", c2)

// Extract real and imaginary parts
fmt.Println("real:", real(c1))
fmt.Println("imag:", imag(c1))

// Complex number operations
fmt.Println("magnitude:", cmplx.Abs(c1)) // 5 (3-4-5 Pythagorean triple)
fmt.Println("sqrt(-1):", cmplx.Sqrt(-1)) // (0+1i)
}

bool Type​

bool has only two possible values: true or false.

package main

import "fmt"

func main() {
var isReady bool = true
var hasError = false

// Logical operators
fmt.Println(true && false) // AND: false
fmt.Println(true || false) // OR: true
fmt.Println(!true) // NOT: false

// Comparison operators return bool
x, y := 10, 20
fmt.Println(x < y) // true
fmt.Println(x == y) // false
fmt.Println(x != y) // true

// Conditional expression
if isReady && !hasError {
fmt.Println("System ready")
}

// In Go, bool and int are different types β€” no implicit conversion
// var n int = true // compile error!
var n int
if isReady {
n = 1
}
fmt.Println("n:", n)
}

string Type​

A Go string is an immutable sequence of bytes, encoded in UTF-8. It can be treated as a byte array ([]byte).

package main

import "fmt"

func main() {
// Double-quoted string β€” escape sequences are processed
s1 := "Hello, World!\n"
fmt.Print(s1) // newline is processed

// Backtick string (raw string) β€” no escape processing
s2 := `First line
Second line
Path: C:\Users\Go`
fmt.Println(s2)

// Strings are immutable β€” you can read individual bytes by index
s3 := "Hello"
fmt.Printf("s3[0] = %d (%c)\n", s3[0], s3[0]) // 72 (H)
// s3[0] = 'h' // compile error! strings are immutable

// String concatenation
hello := "Hello"
world := "World"
greeting := hello + ", " + world + "!"
fmt.Println(greeting)

// String length β€” number of bytes
emoji := "Hello 🌍"
fmt.Printf("len(%q) = %d bytes\n", emoji, len(emoji)) // more than 7 due to UTF-8 encoding
}

byte and rune​

package main

import (
"fmt"
"unicode/utf8"
)

func main() {
// byte = uint8 (a single ASCII character)
var b byte = 'A'
fmt.Printf("byte: %d (%c)\n", b, b) // 65 (A)

// rune = int32 (a Unicode code point)
var r rune = 'δΈ–'
fmt.Printf("rune: %d (%c)\n", r, r) // 19990 (δΈ–)
fmt.Printf("rune hex: U+%04X\n", r) // U+4E16

// Byte count vs character count
s := "Hello, δΈ–η•Œ"
fmt.Printf("byte count: %d\n", len(s)) // 13
fmt.Printf("rune count: %d\n", utf8.RuneCountInString(s)) // 9

// Convert to rune slice to access individual characters
runes := []rune(s)
fmt.Printf("runes[7]: %c\n", runes[7]) // δΈ–
fmt.Printf("runes[8]: %c\n", runes[8]) // η•Œ

// Iterating with range yields runes
for i, r := range s {
fmt.Printf("index %2d: %c (U+%04X)\n", i, r, r)
}
}

Output (partial):

byte count: 13
rune count: 9
runes[7]: δΈ–
index 0: H (U+0048)
index 1: e (U+0065)
...
index 7: δΈ– (U+4E16)
index 10: η•Œ (U+754C)

Numeric Literal Syntax​

Since Go 1.13, various base prefixes and readability separators are supported.

package main

import "fmt"

func main() {
// Decimal (default)
decimal := 1_000_000 // underscores improve readability
fmt.Println("decimal:", decimal) // 1000000

// Binary (0b or 0B prefix)
binary := 0b1010_1100
fmt.Printf("binary: %b = %d\n", binary, binary) // 10101100 = 172

// Octal (0o or 0O prefix, or leading 0)
octal := 0o755
fmt.Printf("octal: %o = %d\n", octal, octal) // 755 = 493

// Hexadecimal (0x or 0X prefix)
hex := 0xFF_EC_D0_12
fmt.Printf("hex: %X = %d\n", hex, hex)

// Floating-point literals
f1 := 1_234.567_89
f2 := 1.5e10 // 1.5 Γ— 10^10
f3 := 0x1p-2 // hexadecimal floating-point (0.25)
fmt.Println(f1, f2, f3)
}

Explicit Type Conversion​

Go has no implicit type conversions. Operations between different types require an explicit conversion using the T(v) syntax.

package main

import "fmt"

func main() {
var i int = 42
var f float64 = 3.14

// int β†’ float64
result := float64(i) + f
fmt.Printf("%.2f\n", result) // 45.14

// float64 β†’ int (truncates the fractional part)
truncated := int(f)
fmt.Println(truncated) // 3

// int β†’ string (warning: this converts the code point, not the number!)
var code int = 65
wrongWay := string(code) // "A" (code point 65 = 'A')
fmt.Println(wrongWay) // A (not the string "65"!)

// To convert a number to its string representation, use fmt.Sprintf or strconv
// rightWay := fmt.Sprintf("%d", code) // "65"

// string ↔ []byte conversion (causes a memory copy)
s := "Hello, Go"
b := []byte(s) // string β†’ []byte
b[0] = 'h' // []byte is mutable!
s2 := string(b) // []byte β†’ string
fmt.Println(s2) // hello, Go

// string ↔ []rune conversion
text := "Hello"
r := []rune(text)
fmt.Printf("first char: %c\n", r[0]) // H
fmt.Printf("char count: %d\n", len(r)) // 5
}

Type Conversion in Practice​

package main

import (
"fmt"
"unsafe"
)

func main() {
// Conversion between integer types
var small int8 = 100
var large int64 = int64(small) // int8 β†’ int64
fmt.Println(large)

// Beware of overflow: large type β†’ small type
var big int = 300
var tiny int8 = int8(big) // 300 exceeds int8 range!
fmt.Println(tiny) // 44 (300 mod 256 = 44, unexpected result!)

// Check type sizes with unsafe.Sizeof
fmt.Println("int8 size:", unsafe.Sizeof(int8(0))) // 1
fmt.Println("int16 size:", unsafe.Sizeof(int16(0))) // 2
fmt.Println("int32 size:", unsafe.Sizeof(int32(0))) // 4
fmt.Println("int64 size:", unsafe.Sizeof(int64(0))) // 8
fmt.Println("float32 size:", unsafe.Sizeof(float32(0))) // 4
fmt.Println("float64 size:", unsafe.Sizeof(float64(0))) // 8
}

Type Alias vs Type Definition​

Go provides two ways to create new type names.

Type Definition​

package main

import "fmt"

// Type definition: creates a completely new type
type Celsius float64
type Fahrenheit float64

func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}

func main() {
temp := Celsius(100.0)
fmt.Printf("%.1fΒ°C = %.1fΒ°F\n", temp, temp.ToFahrenheit())

// Type safety: cannot directly assign between different types
var c Celsius = 100
// var f Fahrenheit = c // compile error! different types
var f Fahrenheit = Fahrenheit(c) // explicit conversion is OK
fmt.Println(f)

// Can also convert back to the underlying type
raw := float64(c)
fmt.Println(raw)
}

Type Alias​

package main

import "fmt"

// Type alias: gives a different name to the same type (Go 1.9+)
type MyString = string // MyString is exactly the same as string

func main() {
var s MyString = "Hello"
var t string = s // no conversion needed since it's an alias
fmt.Println(s, t)

// byte and rune are actually type aliases
// type byte = uint8
// type rune = int32
var b byte = 255
var u uint8 = b // assignable directly since they are aliases
fmt.Println(b, u)
}

Type Definition vs Type Alias​

AspectType Definition (type T U)Type Alias (type T = U)
Creates new typeYesNo (same type)
Can add methodsYesNo
Implicit assignmentNo (explicit conversion needed)Yes (same type)
Common useDomain types, safe APIsCompatibility, refactoring

Real-World Example: Type Conversions​

package main

import (
"fmt"
"math"
"strconv"
)

// Temperature converter
type Celsius float64
type Fahrenheit float64
type Kelvin float64

func celsiusToFahrenheit(c Celsius) Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}

func celsiusToKelvin(c Celsius) Kelvin {
return Kelvin(c + 273.15)
}

func main() {
// string β†’ number conversion (using strconv)
input := "36.6"
tempF64, err := strconv.ParseFloat(input, 64)
if err != nil {
fmt.Println("conversion error:", err)
return
}

temp := Celsius(tempF64)
fmt.Printf("Body temp: %.1fΒ°C = %.1fΒ°F = %.2fK\n",
temp,
celsiusToFahrenheit(temp),
celsiusToKelvin(temp))

// number β†’ string conversion
score := 98
scoreStr := strconv.Itoa(score)
fmt.Println("score string:", scoreStr + " points")

// Type conversions in numeric calculations
total := 7
parts := 3
// Integer division truncates
intDivision := total / parts
// Convert to float64 before dividing
floatDivision := float64(total) / float64(parts)
fmt.Printf("integer division: %d\n", intDivision) // 2
fmt.Printf("float division: %.4f\n", floatDivision) // 2.3333
fmt.Printf("rounded: %.0f\n", math.Round(floatDivision)) // 2
}

Pro Tips​

Tip 1: Use int for general purposes, int64 when size must be guaranteed

By convention the standard library and Go idioms use int for indices and counters. Use int64 for file sizes, Unix timestamps, and network protocols where a specific size must be guaranteed.

Tip 2: Prefer float64 over float32 in most cases

Go's math package is built around float64. Reserve float32 for cases where memory savings matter, such as large numeric arrays or GPU computation.

Tip 3: Use type definitions to prevent unit mistakes

type UserID int64
type OrderID int64

func processOrder(uid UserID, oid OrderID) { ... }

// Accidentally swapping uid and oid is caught at compile time

Tip 4: Check for overflow when converting from a larger type to a smaller type

func safeInt64ToInt32(n int64) (int32, error) {
if n > math.MaxInt32 || n < math.MinInt32 {
return 0, fmt.Errorf("overflow: %d does not fit in int32", n)
}
return int32(n), nil
}