Integers are whole numbers.
const a = 10;
const b = -5;
const c = 0;Zig has many integer types because systems programs often need exact control over size. A number might be stored in one byte, four bytes, eight bytes, or some unusual number of bits. Zig lets you say exactly what you want.
Signed and unsigned integers
Integer types usually start with either i or u.
const x: i32 = -100;
const y: u32 = 100;i32 means a signed 32-bit integer. It can store negative numbers, zero, and positive numbers.
u32 means an unsigned 32-bit integer. It can store only zero and positive numbers.
The same pattern works for other sizes:
| Type | Meaning | Can store negative values |
|---|---|---|
i8 | signed 8-bit integer | yes |
u8 | unsigned 8-bit integer | no |
i16 | signed 16-bit integer | yes |
u16 | unsigned 16-bit integer | no |
i32 | signed 32-bit integer | yes |
u32 | unsigned 32-bit integer | no |
i64 | signed 64-bit integer | yes |
u64 | unsigned 64-bit integer | no |
A bit is a binary digit. It can be 0 or 1. An 8-bit integer uses 8 binary digits. A 32-bit integer uses 32 binary digits.
Ranges
The number of bits decides how many values the type can store.
A u8 has 8 bits. It can store 256 different values:
0 through 255An i8 also has 8 bits, but it must include negative numbers too:
-128 through 127This code is valid:
const a: u8 = 255;
const b: i8 = -128;This code is invalid:
const a: u8 = 256; // too large
const b: i8 = 128; // too large
const c: u8 = -1; // unsigned integers cannot be negativeZig checks these cases. If a value cannot fit into the type, the compiler rejects it.
Arbitrary bit-width integers
Zig also supports integer types with custom bit widths.
const small: u3 = 5;u3 is an unsigned 3-bit integer. It can store values from 0 to 7.
000 = 0
001 = 1
010 = 2
011 = 3
100 = 4
101 = 5
110 = 6
111 = 7This is valid:
const x: u3 = 7;This is invalid:
const x: u3 = 8; // errorCustom-width integers are useful in packed data structures, hardware programming, binary protocols, and file formats. Beginners do not need them often, but they show one of Zig’s goals: exact control over representation.
usize and isize
Zig has two pointer-sized integer types:
usize
isizeusize is an unsigned integer large enough to hold a memory size or array index on the target platform.
isize is the signed version.
You will see usize often with arrays and slices:
const index: usize = 0;When you write loops over arrays, indexes are commonly usize.
const std = @import("std");
pub fn main() void {
const numbers = [_]i32{ 10, 20, 30 };
var i: usize = 0;
while (i < numbers.len) : (i += 1) {
std.debug.print("{}\n", .{numbers[i]});
}
}numbers.len has type usize, so the index i is also a usize.
Integer literals
A number written directly in source code is called an integer literal.
const x = 123;The literal 123 does not immediately have a fixed machine integer type like i32 or u64. Zig can keep it as a compile-time integer until a specific type is needed.
For example:
const a: u8 = 123;
const b: i32 = 123;
const c: u64 = 123;The same literal can be used in different contexts because 123 fits into all those types.
But this fails:
const x: u8 = 300; // error300 does not fit into u8.
Number bases
Zig lets you write integers in different bases.
const decimal = 255;
const binary = 0b11111111;
const octal = 0o377;
const hexadecimal = 0xff;All four values mean the same number: 255.
Different bases are useful in different contexts.
Binary is useful when thinking about individual bits:
const flags = 0b1010;Hexadecimal is common in systems programming because one hex digit represents exactly 4 bits:
const color = 0xff00aa;
const mask = 0xffff0000;You can also use underscores to make large numbers easier to read:
const one_million = 1_000_000;
const mask = 0xffff_0000;The underscores do not change the value.
Basic arithmetic
Integer arithmetic works as expected:
const a: i32 = 10;
const b: i32 = 3;
const sum = a + b;
const difference = a - b;
const product = a * b;
const quotient = @divTrunc(a, b);Zig is explicit about some forms of division. For signed integers, you often use builtins such as @divTrunc, @divFloor, or @divExact, depending on the behavior you want.
For unsigned integers, normal division is simpler:
const a: u32 = 10;
const b: u32 = 3;
const q = a / b; // 3The result is an integer. The fractional part is discarded.
Overflow
Overflow happens when a calculation produces a number too large or too small for the type.
Consider u8.
minimum: 0
maximum: 255Now look at this:
var x: u8 = 255;
x = x + 1;Mathematically, 255 + 1 is 256.
But u8 cannot store 256.
That is overflow.
In safe build modes, Zig checks for overflow and stops the program if overflow happens at runtime. At compile time, Zig rejects overflow when it can prove it.
const x: u8 = 255 + 1; // errorThis is a major safety feature. In C, unsigned overflow wraps around by default. In Zig, ordinary arithmetic is checked in safety-enabled modes.
Wrapping arithmetic
Sometimes overflow is intentional.
For example, low-level code may want values to wrap around like a clock:
254, 255, 0, 1, 2Zig makes this explicit with wrapping operators.
var x: u8 = 255;
x +%= 1;After this, x becomes 0.
Wrapping operators include:
| Operator | Meaning |
|---|---|
+% | wrapping addition |
-% | wrapping subtraction |
*% | wrapping multiplication |
+%= | wrapping addition assignment |
-%= | wrapping subtraction assignment |
*%= | wrapping multiplication assignment |
Example:
const std = @import("std");
pub fn main() void {
var x: u8 = 255;
x +%= 1;
std.debug.print("{}\n", .{x});
}Output:
0The important point is that wrapping is visible in the code. A reader can see that overflow is intentional.
Saturating arithmetic
Sometimes you want a value to stop at the minimum or maximum instead of overflowing.
For example:
250 + 10 = 255for a u8, because 255 is the maximum.
This is called saturating arithmetic.
Zig has saturating operators:
| Operator | Meaning |
|---|---|
| `+ | ` |
| `- | ` |
| `* | ` |
| `+ | =` |
| `- | =` |
| `* | =` |
Example:
const std = @import("std");
pub fn main() void {
var x: u8 = 250;
x +|= 10;
std.debug.print("{}\n", .{x});
}Output:
255The value stops at the maximum instead of wrapping to a small number.
Bitwise operations
Integers can also be treated as groups of bits.
Zig supports common bitwise operators:
| Operator | Meaning |
|---|---|
& | bitwise AND |
| ` | ` |
^ | bitwise XOR |
~ | bitwise NOT |
<< | shift left |
>> | shift right |
Example:
const a: u8 = 0b1100;
const b: u8 = 0b1010;
const both = a & b; // 0b1000
const either = a | b; // 0b1110
const diff = a ^ b; // 0b0110Bitwise operations are common in flags, compression, encryption, binary formats, and hardware registers.
For now, you only need the basic idea: integers can represent numbers, but they can also represent raw bit patterns.
Casting integers
Sometimes you need to convert one integer type to another.
const small: u8 = 10;
const large: u32 = small;Going from a smaller type to a larger compatible type is usually safe.
Going from a larger type to a smaller type may lose information, so Zig requires an explicit cast.
const large: u32 = 200;
const small: u8 = @intCast(large);If the value does not fit, safety checks can catch the problem.
const large: u32 = 1000;
const small: u8 = @intCast(large); // too large for u8Zig wants narrowing conversions to be visible.
Choosing integer types
For ordinary beginner code, these are reasonable defaults:
| Use case | Type |
|---|---|
| array index | usize |
| count or size | usize |
| small byte value | u8 |
| general signed integer | i32 or i64 |
| general unsigned integer | u32 or u64 |
| exact binary field | exact size, such as u16 or u32 |
Do not overthink every small number at first. But when size, layout, or range matters, choose the type deliberately.
The main idea
Integer types in Zig are precise.
A type like u8 does not mean “some small number.” It means exactly an unsigned 8-bit integer. It has a fixed range. It has specific overflow behavior. It has a specific memory representation.
Zig makes these details visible because systems code depends on them.