JavaRush /Courses /Go SELF /Integer types: int, int64, uint

Integer types: int, int64, uint

Go SELF
Level 7, Lesson 1
Available

1. Introduction

When you are just starting to program, you want “a number to be a number”: you write 10 — and that means ten; what other options could there be? I get it. But a computer does not live in the world of abstract mathematics; it lives in the world of bits and finite ranges. Every integer type has limits: the minimum and maximum values it can store. Choosing a type is a way to tell the compiler in advance: “here I have a counter, it won’t be huge,” or “here I have money in cents, I don’t want overflows,” or “here there are no negative values.”

In Go, a type is not decoration for beauty. It is part of correctness. And a good choice of type usually makes code clearer than a comment like // there definitely won’t be a minus here, pinky promise.

Signed and unsigned numbers: int and uint

Simply put, int is “an integer that can be negative,” and uint is “an integer that cannot be negative.” But behind this simplicity is an important detail: signed and unsigned types use the same bits differently. So the exact same “memory slot” can be interpreted either as a range that includes negatives or as a range from zero upward only.

To make it easier on your brain, keep this picture in mind:

int (signed):   ... -3  -2  -1   0   1   2   3 ...
uint (unsigned):           0   1   2   3   4 ...

And here comes some immediately useful everyday logic.

If a value can be negative (temperature, account balance, the difference between two numbers, “error” as -1 — though you should not do it that way), then uint simply does not fit semantically.

If a value cannot be negative (a count of something, a size of something), that still does not mean uint is automatically the best choice. Why? Because in Go, many “size-like” things (for example, len(...)) use int, and it is convenient to align with that.

2. int: the default workhorse type

When you write practice problems, loop counters, indexes, character counts, number of attempts, user age — most of the time your best friend is int. It is “native” to the platform: Go chooses an int size that is natural for the CPU and operating system.

And here is a nuance worth remembering once and then living with: int can be 32-bit or 64-bit, depending on the platform. So int is not “always huge” and not “always small”; it is “platform-sized.”

Go has a built-in hint: strconv.IntSize is the number of bits in the int type on the current platform.

A small diagnostic example:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println("int size:", strconv.IntSize, "bits") // for example: int size: 64 bits
}

Why is int so popular even though it “varies in size”?

Because it is ideal for ordinary program logic: loops, indexing, counting, most learning-level arithmetic. And importantly, the standard library often expects exactly int in “size-related” places (for example, string lengths via len return int).

A mini-example that helps “hardwire” int as the type for counters:

package main

import "fmt"

func main() {
	s := "hello"
	n := len(s)                    // n has type int
	fmt.Printf("%v (%T)\n", n, n)  // 5 (int)
}

4. int64: when you need a fixed 64-bit range

If int is a convenient everyday type, then int64 is the tool you use when you want a fixed size and a large range. It is always 64-bit, regardless of where the program runs. This is useful in situations where you store potentially large numbers or when the data format must be the same across different machines.

Important: you do not have to “write int64 everywhere because it’s bigger.” That does not automatically make a program better. But there is a class of tasks where int64 is truly “more correct semantically.”

Good candidates for int64:

1) Money in the smallest units, for example in cents. The key idea: money is often better stored as integers (for example, “12345 cents”) to avoid float precision issues. And a money amount in cents can become large fairly quickly.

2) Large counters and accumulators: total views, total bytes, large “all-time” values.

3) Numbers coming from the outside world (loosely: “user ID,” “transaction number”), if it is known in advance that they are 64-bit. Even without future topics: seeing an “ID with 18 digits” is normal.

Mini-example: let’s keep a “balance in cents” as int64 so we do not worry about the range:

package main

import "fmt"

func main() {
	var balanceCents int64 = 12500
	balanceCents = balanceCents - 1999
	fmt.Println(balanceCents) // 10501
}

5. uint: when it is actually appropriate

uint is an unsigned integer type that is “platform-sized” (like int, but without negatives). That means it can also be 32-bit or 64-bit. Its purpose is to store non-negative values with the largest possible range for that size.

And here is the trap: “if there are no negative values, then we should use uint.” In practice, this often leads to more complicated code. The main reason is simple: in real program logic, negative numbers sometimes appear “along the way.” For example, you compute the difference between two values, decrement a counter, do x - 1 — and suddenly subtraction becomes a source of surprises if x is uint.

So for a beginner and for ordinary application logic, the rule is: by default, use int, and use uint when you have a clear semantic reason or a requirement.

Typical situations where uint shows up naturally:

  • working with bit masks and low-level things (we are not covering that today, but you will see it);
  • when an API (yours or a library’s) already requires uint, and you adapt;
  • when you explicitly model a “non-negative identifier/size” and you are sure your computations will not produce negative intermediate values.

There is one more nuance that looks like “compiler magic,” but is really just strict rules. By default, the numeric constant 17 has type int, not uint. For integer constants, the “default type” is int, and there is no “default type = unsigned” for constants.

So if you need a uint, you must say so explicitly in code:

package main

import "fmt"

func main() {
	u := uint(17)
	fmt.Printf("%v (%T)\n", u, u) // 17 (uint)
}

That explicit type is normal practice; it makes the code more honest. This is exactly the case where Go says: “I won’t guess for you.” And that is good.

6. Bit width and ranges: how to evaluate a type

When you choose an integer type, it is useful to think not “this will definitely be enough,” but “what range does this have?” Any integer type is a fixed number of bits, which means a fixed number of distinct values.

A quick semantic cheat sheet:

Type Sign Size Range idea
int
signed 32 or 64 bits “convenient workhorse”
uint
unsigned 32 or 64 bits “convenient workhorse without minus”
int64
signed 64 bits “fixed wide”

We are not looking at narrow types like int8 and uint8 today, but as an illustration of ranges they are useful. For example, int8 has a range from -128 to 127, and uint8 has a range from 0 to 255. This helps you internalize the idea that “a type restricts possible values,” using small numbers where the limits are easy to remember.

If you just thought, “then why not write int64 everywhere; the range is big?” — that is a normal question. But in Go it is not only about “does the number fit”; it is also about readability, consistency of types in expressions, and what types standard library functions expect. If you have a loop counter, int looks natural. If you have money in cents or a large accumulating counter, int64 is more logical.

7. Example: FinTrack and choosing types

So this does not stay theory, let’s continue our learning storyline: a small console program FinTrack (a hypothetical “budget tracker”). Right now it can read income and expense and print the remaining balance. Today our goal is not to build “perfect accounting,” but to choose types so the program is predictable.

We will agree on a simple data rule: enter money in cents, so we stay in integers and do not argue with float. That immediately nudges us toward int64: the balance can be negative (you went below zero), and we want a wide range.

A minimal code example:

package main

import "fmt"

func main() {
	var incomeCents int64
	var expenseCents int64

	fmt.Scan(&incomeCents, &expenseCents)

	balanceCents := incomeCents - expenseCents
	fmt.Println(balanceCents) // for example: 10501
}

Notice that int can also show up here, but in a different place: if later we want, for example, to “repeat input N times” or count the number of operations, that will naturally be an int counter in a loop. But the money itself, as a value that can grow and can be negative, lives in int64.

And one more small habit that saves you from confusion: sometimes it is useful to temporarily print a variable’s type via %T to make sure you did not accidentally end up with int where you expected int64.

package main

import "fmt"

func main() {
	var incomeCents int64 = 10000
	fmt.Printf("%v (%T)\n", incomeCents, incomeCents) // 10000 (int64)
}

8. Common mistakes when choosing int, int64, uint

Mistake #1: “Let’s use uint everywhere since there are no negative numbers.”

In real program logic, negative values often appear as an intermediate result: differences, decrements, adjustments, “add/subtract.” If you force everything to be uint, you will either start constantly converting types (and get annoyed at the compiler), or one day you will get very strange behavior when subtracting near zero. uint is good where it truly matches the meaning, not where you “want to forbid minus.”

Mistake #2: expecting the literal 17 to become uint by itself.

For integer constants, the default type is int, and there is no “automatic unsigned version.” So u := 17 is int, not uint. If you need uint, you must explicitly write uint(17) or declare the variable as var u uint = 17.

Mistake #3: “int is always 64-bit, so everything is fine.”

On many modern machines that will be true, but by definition int depends on the platform: 32 or 64. If you are writing code where a large range or a fixed format matters, it is better to use int64. And if you just want to know what int you have right now, there is strconv.IntSize.

Mistake #4: using int64 everywhere “just in case” and ending up with a mess of types.

int64 is not bad by itself. But when int, int64, and uint are mixed throughout the code, you get constant type mismatches in expressions and extra conversions. The rule “default to int, and use int64 where needed” usually yields cleaner code than “convert everything to int64 and forget it.”

Mistake #5: choosing a type “for memory size” instead of for meaning.

Beginners sometimes think: “I’ll use int8 because it saves memory.” In practice you save almost nothing, but you greatly increase the chance of hitting the range limits. Narrow types (int8, uint8) are useful in special tasks and as a demonstration of ranges (for example, int8 from -128 to 127, uint8 from 0 to 255). In everyday logic they more often get in the way than help.

1
Task
Go SELF, level 7, lesson 1
Locked
Number Passport
Number Passport
1
Task
Go SELF, level 7, lesson 1
Locked
Rewards counter
Rewards counter
1
Task
Go SELF, level 7, lesson 1
Locked
Wallet balance
Wallet balance
1
Task
Go SELF, level 7, lesson 1
Locked
Free slots
Free slots
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION