Fix

"constant overflows int" in Go

This error occurs when a Go constant expression exceeds the maximum value of the target integer type (usually `int` on your specific architecture) during compile-time evaluation.

The architecture trap

You write a constant for a user ID, a timestamp, or a cryptographic nonce. You test it on your laptop. Everything compiles. You deploy to a legacy server or a Raspberry Pi. The build fails with a blunt compiler message: constant overflows int. The code worked yesterday. The number hasn't changed. The machine did.

This is not a bug in your code. It is Go refusing to silently truncate your data. The compiler caught a mismatch between your mathematical value and the storage size available on the target architecture. Fixing it requires understanding how Go treats numbers before they ever run.

How Go evaluates numbers before they run

Go constants are untyped until you force a type onto them. When you write const MaxItems = 5000000000, the compiler does not store that as an int, int64, or float64. It stores it as a pure mathematical value in its internal representation. Untyped constants can hold arbitrary precision. They only get squeezed into a concrete type when you assign them to a variable, pass them to a function, or use them in an expression.

The moment you use an untyped constant in a context that expects int, the compiler checks the architecture. On 64-bit systems, int is 64 bits wide and can hold values up to roughly 9 quintillion. On 32-bit systems, int is 32 bits wide and caps at 2,147,483,647. If your constant exceeds that ceiling, the compiler stops the build. It will not silently drop the high bits. It will not wrap around. It throws a hard error.

This design choice keeps Go predictable. You never get silent data loss from integer overflow at compile time. The tradeoff is that you must be explicit when your values cross architecture boundaries.

A minimal example

Here is the exact pattern that triggers the error, followed by the two standard fixes.

package main

import "fmt"

// Untyped constant exceeds 32-bit int max value
const LargeValue = 3000000000

func main() {
	// The compiler tries to fit LargeValue into int.
	// On 32-bit systems, this fails at compile time.
	// var x int = LargeValue

	// Fix 1: Assign to a variable with an explicit wider type.
	// The compiler sees int64 and knows the constant fits.
	var x int64 = LargeValue

	// Fix 2: Give the constant a concrete type at declaration.
	// This removes the untyped flexibility and locks the size.
	const LargeValueTyped int64 = 3000000000

	fmt.Println(x)
	fmt.Println(LargeValueTyped)
}

The compiler evaluates LargeValue at build time. When it sees var x int = LargeValue, it asks whether 3,000,000,000 fits in a 32-bit signed integer. It does not. The build fails with constant 3000000000 overflows int. Changing the variable type to int64 gives the compiler a container large enough to hold the value. Giving the constant a type at declaration does the same thing, but it also removes the constant's untyped flexibility. You can no longer assign it to a uint32 or use it in a floating-point expression without an explicit conversion.

Pick the approach that matches your intent. Keep constants untyped when you want the compiler to infer the best fit for each usage site. Lock the type when the size is part of the contract.

Bit shifts and hidden overflows

Bitwise operations are the most common place this error hides. A shift like 1 << 32 looks harmless. It produces 4,294,967,296. On a 64-bit machine, that fits in int easily. On a 32-bit machine, it does not. The compiler evaluates the shift at compile time because both operands are constants. It calculates the result, checks the target type, and rejects the program if the result overflows.

package main

import "fmt"

// Mask32Bit tries to shift a 1 into the 33rd bit position.
// The result exceeds the 32-bit signed int maximum.
func main() {
	// This line fails on 32-bit builds with:
	// constant 4294967296 overflows int
	// const Mask32Bit = 1 << 32

	// Fix: Type the literal before shifting.
	// The compiler now treats the left operand as int64,
	// performs the shift in 64-bit space, and stores the result.
	const Mask32Bit int64 = 1 << 32

	fmt.Println(Mask32Bit)
}

The key detail is that the type annotation applies to the entire constant expression. When you write const Mask32Bit int64 = 1 << 32, the compiler promotes the 1 to int64, performs the shift, and verifies the result fits in int64. If you only type the variable later, the shift already happened in untyped space, and the compiler still checks against the default int size during assignment.

Go evaluates constant expressions eagerly. This means you get compile-time guarantees for bitmasks, flags, and cryptographic constants. The downside is that you must be deliberate about operand types. A community convention exists for this: always type constants that participate in bitwise operations or exceed 2^31. It prevents architecture-dependent surprises and makes the intent obvious to anyone reading the code.

When to pick which integer type

Go gives you several integer sizes. The right choice depends on what the number represents and where it will run.

Use int when you are counting items, indexing slices, or working with loop counters. The compiler will automatically match the platform's native word size, which gives the best performance for arithmetic and memory alignment.

Use int64 when the value must survive across architectures, when you are storing timestamps, when you are handling external IDs that exceed 2.1 billion, or when you are performing bit shifts that touch positions above 31.

Use untyped constants when you want the compiler to infer the most efficient type at each usage site. Keep them untyped for small literals, configuration thresholds, and values that might be assigned to different types in different parts of the codebase.

Use explicit type conversions when you must bridge between sized types. Go does not perform implicit widening or narrowing. Write int64(value) when you know the data fits, and handle the mismatch when it does not.

Use uint or uint64 only when you are working with unsigned mathematics, cryptographic hashes, or network protocols that specify unsigned integers. Stick to signed integers for general application logic to avoid unexpected wraparound behavior.

Where to go next