What Is the Difference Between int, int32, and int64 in Go

int is platform-dependent, while int32 and int64 are fixed-width types ensuring consistent size across all systems.

The size trap

You write a counter for a service. It runs perfectly on your laptop. You deploy it to a fleet of embedded devices, and suddenly the count wraps around to negative numbers at two billion. The code didn't change. The hardware did. This is the trap of Go's int type.

Go gives you int as a convenience. It matches the pointer size of the machine. On a 64-bit machine, pointers are 64 bits, so int is 64 bits. On a 32-bit machine, int is 32 bits. int32 and int64 are anchors. They are always 32 or 64 bits, no matter what.

The choice between them isn't just about how big a number you can store. It affects memory layout, cross-compilation behavior, and how your code talks to external systems. Pick the wrong one and you get silent data corruption or compiler errors that only appear on the build server.

int matches the machine. Fixed types match the contract.

Native speed versus fixed precision

Processors have a native word size. On a 64-bit CPU, the registers are 64 bits wide. Arithmetic on 64-bit values usually takes one instruction. Arithmetic on 32-bit values also takes one instruction because the hardware handles it efficiently. But if you force a 64-bit operation on a 32-bit CPU, the processor might need multiple instructions or software emulation to stitch the result together.

int exists to let you write code that uses the native word size without thinking about it. Loop counters, slice indices, and local arithmetic should almost always use int. The compiler generates the fastest possible machine code for the target architecture.

int32 and int64 exist when the size matters more than the speed. Binary protocols, file formats, and database schemas often mandate exact byte counts. A protocol that says "the ID is four bytes" requires int32. If you use int and deploy to a 64-bit machine, the ID becomes eight bytes. The wire format breaks. The downstream service rejects the message.

Fixed-width types also protect you from overflow assumptions. If you document that a field holds a timestamp in nanoseconds, int64 guarantees you have enough room. int might have enough room on your Mac, but it fails on a 32-bit Raspberry Pi.

Checking the size

You can inspect the size of types at runtime using the unsafe package. This is useful for debugging or verifying assumptions, though you rarely need it in production code.

Here's how to print the bit width of each integer type.

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// unsafe.Sizeof returns the size in bytes.
	// Multiply by 8 to convert to bits.
	fmt.Printf("int is %d bits\n", unsafe.Sizeof(int(0))*8)
	fmt.Printf("int32 is %d bits\n", unsafe.Sizeof(int32(0))*8)
	fmt.Printf("int64 is %d bits\n", unsafe.Sizeof(int64(0))*8)
}

On a 64-bit system, this prints:

# output:
int is 64 bits
int32 is 32 bits
int64 is 64 bits

On a 32-bit system, int drops to 32 bits while int32 and int64 stay the same. The unsafe package is part of the standard library, but use it sparingly. It bypasses the type system's safety guarantees. Most of the time, you rely on the compiler to enforce sizes, not runtime checks.

The cross-compilation reality

Go makes cross-compilation trivial. You can build a binary for a different architecture by setting environment variables. This is where int bites developers who assume their local machine represents the world.

If you run GOARCH=386 go build, the compiler targets a 32-bit x86 CPU. Every int in your code becomes 32 bits. If your code assumes int can hold a value larger than two billion, the binary will overflow on that architecture. The compiler won't warn you. It trusts that you know what int means for the target.

This matters for libraries. If you publish a package that uses int for a public API, consumers on 32-bit systems get a different interface than consumers on 64-bit systems. The API contract changes based on the build target. This is usually a mistake. Public APIs should use fixed-width types if the size is part of the contract.

Test your code on the target architecture. int is a liar if you assume it's 64 bits everywhere.

Memory layout and padding

Structs in Go are laid out in memory based on the alignment requirements of their fields. The compiler inserts padding bytes to ensure each field starts at an address that matches its alignment. This affects the total size of the struct and can waste memory if you aren't careful.

Here's a struct that mixes types and reveals padding behavior.

package main

import (
	"fmt"
	"unsafe"
)

type Config struct {
	// Flag is one byte.
	Flag byte
	// ID is exported for JSON marshaling.
	// On 64-bit systems, int is 64 bits and requires 8-byte alignment.
	// The compiler inserts 7 bytes of padding after Flag.
	ID int
	// Count is a local counter.
	Count int
}

func main() {
	// Print the total size of the struct.
	// On 64-bit, this is 24 bytes: 1 + 7 padding + 8 + 8.
	fmt.Printf("Config size: %d bytes\n", unsafe.Sizeof(Config{}))
}

Reordering fields by size reduces padding. Putting Flag at the end moves the padding to the tail, which the compiler often trims. Memory optimization is rarely the first priority, but in high-throughput services with millions of allocations, struct layout adds up.

Public names start with a capital letter. The ID field is exported so JSON can access it. Private fields stay lowercase. The receiver name for methods on this struct would be (c *Config), following the convention of short names that match the type.

Reorder struct fields by size to save memory. The compiler pads for alignment, not for you.

JSON and the overflow edge case

JSON doesn't have integer types. It has numbers. When Go unmarshals JSON into a struct, it tries to fit the number into the target field. If the number is too large, the unmarshaler returns an error. If you use int and the JSON contains a 64-bit value, the unmarshal fails on 32-bit systems.

This is a common pain point when consuming APIs that return large IDs or timestamps. The API might return 1234567890123456789. Your struct has ID int. On a 64-bit machine, it works. On a 32-bit machine, it crashes with an overflow error.

Use int64 for fields that come from external sources where the range isn't guaranteed. If you control both sides, you can coordinate types. If you consume a third-party API, assume the worst and use the widest type that makes sense.

JSON numbers are strings until you parse them. Pick the type that holds the value.

Compiler errors and conversions

Go does not allow implicit conversions between integer types. You can't assign an int64 to an int variable, even if the value fits. The compiler rejects the code to prevent accidental truncation.

The compiler complains with cannot use x (type int64) as type int in argument if you try to pass an int64 where an int is expected. You must write an explicit conversion: int(x). The conversion is a promise to the compiler that you've checked the value and accept the risk of overflow.

If you convert a large int64 to int32, the value wraps around. The compiler doesn't check the value at compile time. It trusts your conversion. Runtime panics don't happen on overflow; you just get the wrong number. This is why fixed-width types are safer for data that crosses boundaries. You can verify the size at the boundary and convert once, rather than leaking assumptions through the codebase.

Go forces you to write the cast. The cast is a promise that the value fits.

Decision matrix

Use int for loop counters, slice indices, map keys, and local arithmetic where the value fits in a machine word. It generates the fastest code and matches the idiomatic style of the standard library.

Use int32 when you are interfacing with a C library that expects a 32-bit integer, or when writing a binary protocol that mandates 4-byte fields. Use it for database columns defined as INT or INTEGER where the schema guarantees 32 bits.

Use int64 when you need to store timestamps in nanoseconds, handle file offsets larger than 4GB, or interface with 64-bit fixed-width APIs. Use it for JSON fields that might contain large IDs from external services.

Use uint or uint64 when you are dealing with bitmasks, cryptographic hashes, or values that must never be negative. Signed integers are safer for general counting because they detect underflow, but unsigned types are necessary for bitwise operations and hash functions.

Where to go next