Educational Blog

How to Use Integers in C

A practical guide to integer types, literals, overflow, and everyday C usage.

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:

TypeTypical useSigned?
intGeneral-purpose arithmetic and loop countersUsually yes
shortSmall integer storageUsually yes
longLarger range than int on many systemsUsually yes
long longVery large integer rangesUsually yes
unsigned int and related formsNon-negative counts, bit work, sizes in some APIsNo

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, or attempts.

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:

  • U for unsigned
  • L for long
  • LL for 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:

  1. Use a wider type when values can grow.
  2. Check user input before converting or adding.
  3. Guard multiplication and addition when computing buffer sizes.
  4. Prefer size_t for 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 caseGood defaultWhy
General arithmeticintSimple and readable
Large counterslong long or wider fixed-width typeMore range
Array sizes and indexingsize_tMatches object size conventions
Bit masksunsigned int or fixed-width unsigned typeClear bit behavior
Small storage with known rangeuint8_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_t
  • int16_t
  • int32_t
  • int64_t
  • uint8_t
  • uint16_t
  • uint32_t
  • uint64_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:

  1. Can the value ever be negative?
  2. What is the largest plausible value?
  3. Is the value a count, a size, a bit field, or a general arithmetic number?
  4. Will it cross an API boundary or file format boundary?
  5. 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

SituationRecommended approach
Ordinary loop counterint
Possible negative valuessigned integer type
Non-negative flags or bit masksunsigned integer type
Memory sizes and array lengthssize_t
Cross-platform binary layoutfixed-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.

Written by

c-double.com Editorial Team

Editorial team

c-double.com publishes practical how-to guides and educational articles with clear steps and useful context.