The Direct Answer: Which Arduino Variable Type Should You Pick?

The biggest mistake hobbyists make is relying on generic C++ types like int or long. On 8-bit AVR boards (like the Uno or Nano), an int is 16 bits, but on 32-bit ARM/ESP32 boards, an int is 32 bits. This cross-platform inconsistency causes silent overflows and memory bugs when you port code.

The professional standard is to stop using generic types and explicitly declare bit-widths using the <stdint.h> library. Use the decision tree below to pick the exact type for your next build.

Arduino Variable Type Decision Tree
What are you storing? Value Range Needed Do NOT Use Concrete Pick (Use This)
Pin numbers, boolean states, small counters 0 to 255 int, byte uint8_t
ADC readings (10-bit/12-bit), PWM duty cycles 0 to 4,095 int uint16_t
Encoder positions, timestamps (millis), large sums -2.14B to +2.14B int, long int32_t
Fractional math, PID control, sensor scaling Decimals double (on AVR) float (32-bit IEEE 754)
Default Recommendation: Always include #include <stdint.h> at the top of your sketch. Default to int32_t for any counter or math variable unless you are strictly optimizing for SRAM on an 8-bit board, in which case use uint16_t or uint8_t.

Project Build: High-Resolution Encoder Position Tracker

To demonstrate why explicit variable types matter, we are building a rotary encoder tracker. Encoders generate thousands of pulses; if you use a standard 16-bit int on an AVR board, your counter will overflow and wrap from 32,767 to -32,768 in seconds. We will use an interrupt-driven approach, which also requires the volatile qualifier.

Parts List

  • Microcontroller: Arduino Nano V3 (ATmega328P, 16MHz, 5V logic)
  • Sensor: KY-040 Rotary Encoder Module (with breakout board and pull-up resistors)
  • Display: 0.96-inch SSD1306 I2C OLED (128x64, 4-pin)
  • Wiring: 22 AWG solid core jumper wires, half-size solderless breadboard

Pin Mapping Table

Component Module Pin Arduino Nano V3 Pin Notes
KY-040 Encoder CLK D2 (INT0) Must be a hardware interrupt pin
KY-040 Encoder DT D4 Standard digital read
KY-040 Encoder SW D5 Pushbutton (optional)
KY-040 Encoder VCC / GND 5V / GND Module has onboard pull-ups
SSD1306 OLED SDA A4 I2C Data (add 4.7k pull-up if missing)
SSD1306 OLED SCL A5 I2C Clock

Difficulty Rating: 2/5 (Beginner-Intermediate) | Time: 20 minutes

Complete Compilable Code with Type-Safe Counters

This code targets the Arduino Nano V3 (ATmega328P). It requires the Adafruit_SSD1306 and Adafruit_GFX libraries installed via the Library Manager. Notice the strict use of stdint types and the volatile keyword for the interrupt service routine (ISR) variable.

#include <stdint.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- Pin Definitions (uint8_t saves RAM compared to int) ---
const uint8_t PIN_ENC_CLK = 2;   // Hardware interrupt 0
const uint8_t PIN_ENC_DT = 4;
const uint8_t PIN_ENC_SW = 5;
const uint8_t PIN_LED_ERR = 13;  // Onboard LED for error signaling

// --- Display Definitions ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- State Variables ---
// volatile is REQUIRED for variables modified inside an ISR
// int32_t prevents the 16-bit overflow wrap-around on AVR boards
volatile int32_t encoder_position = 0;
volatile bool encoder_updated = false;

void setup() {
  pinMode(PIN_ENC_DT, INPUT);
  pinMode(PIN_ENC_SW, INPUT_PULLUP);
  pinMode(PIN_LED_ERR, OUTPUT);

  // Initialize I2C Display with Error Handling
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    // Halt and blink LED if OLED fails to initialize
    while(true) {
      digitalWrite(PIN_LED_ERR, HIGH);
      delay(250);
      digitalWrite(PIN_LED_ERR, LOW);
      delay(250);
    }
  }

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 20);
  display.print('Ready');
  display.display();
  delay(500);

  // Attach Interrupt (Trigger on falling edge of CLK)
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_CLK), readEncoder, FALLING);
}

void loop() {
  // Only update display when the ISR flags a change (saves I2C bus time)
  if (encoder_updated) {
    encoder_updated = false;
    
    // Create a local copy to prevent tearing if ISR fires during print
    int32_t local_pos = encoder_position; 
    
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.print('Position:');
    
    display.setTextSize(2);
    display.setCursor(0, 25);
    display.print(local_pos);
    display.display();
  }
}

// --- Interrupt Service Routine ---
void readEncoder() {
  // Read DT pin to determine direction
  uint8_t dt_state = digitalRead(PIN_ENC_DT);
  
  if (dt_state == HIGH) {
    encoder_position++;
  } else {
    encoder_position--;
  }
  encoder_updated = true;
}

Debugging Variable Overflows: Exact Errors and Ranked Causes

When you use the wrong variable type, the compiler might catch it, or worse, it might fail silently at runtime. Here is how to debug the most common variable-related failures.

The Compiler Warning

If you try to assign a large constant to a 16-bit integer (e.g., int timeout = 60000; on an Uno), the compiler will throw this exact error string:

warning: overflow in implicit constant conversion [-Woverflow]

The Fix: Change the type to uint16_t (max 65,535) or int32_t. Alternatively, append the UL suffix to the constant (60000UL) if the variable is already correctly typed but the compiler is misinterpreting the literal.

The Runtime Wrap-Around (Silent Failure)

If your serial monitor or display suddenly jumps from 32767 to -32768, you have hit the 16-bit signed integer ceiling. The binary representation of 32767 is 01111111 11111111. Adding one flips the sign bit, resulting in 10000000 00000000 (-32768 in two's complement).

The First 3 Things to Check When Math Fails

  1. Check your board architecture: Are you compiling for AVR (Uno/Nano/Mega) where int is 16-bit, or ARM/ESP32 where int is 32-bit? Never assume int size. Switch to int32_t.
  2. Check for signed/unsigned mismatch: If you compare a signed int32_t with an unsigned uint32_t (like the return value of millis()), the compiler promotes the signed variable to unsigned. A negative number becomes a massive positive number, breaking your logic. Use unsigned long or uint32_t for all time-tracking variables.
  3. Check ISR volatility: If a variable is updated inside an attachInterrupt function but read in loop(), it must be declared volatile. Without it, the GCC compiler optimizes the read out of the loop, caching the initial value in a CPU register and ignoring hardware updates.

SRAM Profiling: Why 8-Bit AVR Boards Run Out of Memory

The ATmega328P on the Arduino Nano V3 has exactly 2,048 bytes of SRAM. This must hold your global variables, the heap (dynamic allocations like String objects), and the stack (local variables inside functions). Using the wrong variable types bloats your globals and causes stack collisions, leading to random reboots.

Memory Footprint per Variable Type (AVR vs 32-bit ARM)
Data Type AVR (Uno/Nano) Size ARM/ESP32 Size Max Value (Signed)
bool / uint8_t 1 byte 1 byte 255 (unsigned)
int 2 bytes 4 bytes 32,767 (AVR) / 2.14B (ARM)
int32_t / long 4 bytes 4 bytes 2,147,483,647
float 4 bytes 4 bytes ~3.4 x 10^38 (7 digits precision)
double 4 bytes (Same as float!) 8 bytes ~1.7 x 10^308 (15 digits precision)
Warning on Floats: The ATmega328P lacks a hardware Floating Point Unit (FPU). Every float calculation is emulated in software, consuming significant Flash memory and CPU cycles. If you are doing PID control or sensor averaging on a Nano, multiply your values by 100 or 1000 and use int32_t fixed-point math instead. It is exponentially faster and saves SRAM.

How to Extend or Simplify the Build

Depending on your project constraints, you can scale this encoder tracker up or down.

Simplify: Drop the I2C Display

If you are building a hidden mechanism where visual feedback isn't needed, remove the SSD1306 OLED and the Adafruit libraries. This immediately frees up roughly 1,500 bytes of Flash and 150 bytes of SRAM used by the display buffer. Replace the display logic in loop() with a simple Serial.println(local_pos);.

Extend: Port to the ESP32-C3 for High-Speed Tracking

If you are tracking a high-RPM motor, the 16MHz ATmega328P might miss interrupts. Port the code to an ESP32-C3 SuperMini.

  • Variable shifts: On the ESP32, a standard int is 32 bits. However, stick to int32_t to maintain code portability.
  • Interrupt handling: The ESP32 handles interrupts differently. You will need to use portENTER_CRITICAL_ISR if you are scaling to multi-core ESP32 variants to prevent race conditions when reading the encoder state.
  • Memory: With 400KB+ of SRAM, you can safely add WiFi logging via MQTT without worrying about the 2KB AVR limit.

By enforcing strict, explicit variable typing via <stdint.h>, you eliminate the most common class of embedded bugs: the silent overflow. Your code will compile cleanly, run predictably, and port effortlessly between 8-bit and 32-bit architectures.

References: Arduino Language Reference: Data Types, AVR Libc: Integer Types (stdint.h), GCC Warning Options Documentation.