Integers are the backbone of most C programs. They hold counters, sizes, loop indexes, return codes, bit masks, array positions, and the results of arithmetic you do over and over again. If you can read, store, and compute with integers comfortably, a lot of everyday C code becomes easier to understand.
This guide focuses on practical use. You will see how integer values work in memory, which integer types C gives you, how literals are interpreted, when overflow becomes a problem, and how to choose the right type for a task. The goal is not to memorize a table and move on. The goal is to use integers confidently in real programs.
What an integer is in C
An integer is a whole number type. In C, that means values without a fractional part, such as 0, 7, -12, or 1024. The language gives you several integer types so you can balance range, speed, and memory use.
The most common types are:
| Type | Typical use | Signed? |
|---|---|---|
int | General-purpose arithmetic and loop counters | Usually yes |
short | Small integer storage | Usually yes |
long | Larger range than int on many systems | Usually yes |
long long | Very large integer ranges | Usually yes |
unsigned int and related forms | Non-negative counts, bit work, sizes in some APIs | No |
The exact size of each type is implementation-defined, so you should avoid assuming that int is always 32 bits or that long is always bigger than int. C guarantees minimum ranges, not a single fixed layout across every platform.
Choosing between signed and unsigned
Signed integers can represent negative and positive values. Unsigned integers only represent zero and positive values, but they usually give a larger positive range for the same storage width.
That sounds simple, but the choice affects more than range:
- Use signed integers when negative values are meaningful.
- Use unsigned integers when the value can never be negative and you want to make that rule explicit.
- Be careful with mixed signed and unsigned expressions, because the rules can change how comparisons and arithmetic behave.
A common beginner mistake is to use unsigned types for everything because they look “safer.” In practice, unsigned values can create surprising bugs when you subtract, compare against zero, or mix them with signed values.
For example:
unsigned int a = 3;
unsigned int b = 5;
unsigned int c = a - b;
c does not become -2. It wraps around to a large positive value instead. That behavior is defined for unsigned arithmetic, but it is often not what you wanted.
Declaring and initializing integers
In C, you declare an integer by naming its type and variable name:
int count;
int score = 42;
long distance = 120000L;
unsigned int flags = 0;
If you do not initialize a local variable, its value is indeterminate. That means you should not read it before assigning something to it. In real code, initialize integers as soon as you declare them unless there is a specific reason not to.
Good habits:
- Initialize counters to zero.
- Initialize flags to zero before setting bits.
- Initialize accumulators before loops.
- Prefer clear names like
count,index,total, orattempts.
Integer literals and suffixes
A number written directly in code is an integer literal. By default, a literal such as 10 is usually treated as int if it fits. Larger literals or special suffixes can change the type.
Examples:
int a = 10;
long b = 10L;
unsigned int c = 10U;
long long d = 10000000000LL;
Useful suffixes include:
Ufor unsignedLfor longLLfor long long
You only need suffixes when the default type would not be what you want or when you want to make intent explicit. For most small integers, plain literals are fine.
Arithmetic with integers
Integer arithmetic in C is straightforward for addition, subtraction, multiplication, division, and remainder.
int x = 17;
int y = 5;
int sum = x + y;
int diff = x - y;
int product = x * y;
int quotient = x / y;
int remainder = x % y;
A few details matter:
- Integer division truncates toward zero.
- The
%operator gives the remainder. - Division by zero is undefined behavior and must be avoided.
- Overflow with signed integers is undefined behavior.
That last point deserves attention. If a signed integer grows beyond its representable range, the program is no longer behaving reliably. This is one reason to choose types carefully and to check bounds before doing arithmetic in safety-critical code.
Integer overflow and underflow
Overflow is what happens when a value is too large for its type. Underflow is the same idea on the low end.
For signed integers, overflow is a serious correctness issue because the language does not define the result. For unsigned integers, arithmetic wraps around modulo the type’s range.
That difference matters in real programs:
- A loop counter that wraps can run too long or too short.
- A size calculation can become tiny after wrapping, leading to an under-allocation bug.
- A comparison can produce the wrong result if signed and unsigned values are mixed.
Practical ways to reduce risk:
- Use a wider type when values can grow.
- Check user input before converting or adding.
- Guard multiplication and addition when computing buffer sizes.
- Prefer
size_tfor object sizes and array indexing in modern C interfaces.
Using integers in control flow
Integers are heavily used in for, while, and if statements.
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
Here, i is a loop counter. That is one of the most common integer patterns in C. The variable starts at zero, increases by one, and stops when it reaches a limit.
You can also use integers as boolean-like values, although modern C usually prefers <stdbool.h> when the codebase allows it. Historically, 0 meant false and any non-zero value meant true.
int ready = 1;
if (ready) {
puts("Go");
}
That works, but for readability, a dedicated boolean type is often clearer when the compiler and code style support it.
Bitwise work with integers
Integers are also the standard storage unit for bitwise operations. That is useful for flags, masks, permissions, packed state, and hardware work.
unsigned int flags = 0;
flags |= 1U << 2;
flags &= ~(1U << 2);
if (flags & (1U << 2)) {
puts("Bit is set");
}
Bitwise operations are a major reason unsigned types are common in low-level code. The behavior is usually easier to reason about when you are treating the value as a sequence of bits rather than as a signed number.
Common integer types by use case
| Use case | Good default | Why |
|---|---|---|
| General arithmetic | int | Simple and readable |
| Large counters | long long or wider fixed-width type | More range |
| Array sizes and indexing | size_t | Matches object size conventions |
| Bit masks | unsigned int or fixed-width unsigned type | Clear bit behavior |
| Small storage with known range | uint8_t, uint16_t, etc. | Predictable width |
The best type depends on the job. If you are counting items in a loop, int is usually fine. If you are storing file sizes or memory lengths, a size-related type is more appropriate. If you are encoding bits, unsigned types are usually a better fit.
Fixed-width integer types
When you need the same size on every platform, use the types from <stdint.h>:
int8_tint16_tint32_tint64_tuint8_tuint16_tuint32_tuint64_t
These types are valuable when writing binary protocols, file formats, embedded software, and code that depends on exact widths.
Example:
int32_t temperature = -12;
uint32_t packet_length = 128;
Use them when width matters more than the natural type of the platform. If the exact-width type does not exist on a target system, the compiler will not define it, which is a useful signal that the platform cannot support your assumption.
Input, output, and formatting
When reading or printing integers, the format specifier must match the type.
int value = 25;
printf("%d\n", value);
For unsigned integers, use %u. For long values, use %ld or %lu. For long long values, use %lld or %llu.
Getting this wrong can produce incorrect output or undefined behavior. This is a common place where code compiles but still acts unpredictably.
A practical workflow for integer-heavy code
When you are not sure which integer type to use, work through this checklist:
- Can the value ever be negative?
- What is the largest plausible value?
- Is the value a count, a size, a bit field, or a general arithmetic number?
- Will it cross an API boundary or file format boundary?
- Do you need a fixed width across platforms?
A simple rule of thumb helps:
- Start with the smallest type that safely fits the real range.
- Prefer readability over cleverness.
- Use fixed-width types only when portability or binary layout matters.
- Check arithmetic when results affect memory allocation or indexing.
Summary table
| Situation | Recommended approach |
|---|---|
| Ordinary loop counter | int |
| Possible negative values | signed integer type |
| Non-negative flags or bit masks | unsigned integer type |
| Memory sizes and array lengths | size_t |
| Cross-platform binary layout | fixed-width integers |
Final advice
Integers in C are easy to start using and easy to misuse if you rely on defaults without thinking. The safe habit is to match the type to the real meaning of the value, not just the syntax of the code around it.
If the value is a count, a size, a bit field, or a math result, ask what can go wrong when it gets larger, smaller, or combined with another type. That question catches many of the bugs that show up later in C programs.