The Ultimate Arduino Data Types Quick Reference

Whether you are programming a classic 8-bit ATmega328P on an Arduino Uno R3 or deploying a dual-core ESP32-S3 for edge computing in 2026, understanding memory allocation is non-negotiable. Mismanaging Arduino data types leads to the most notorious microcontroller bugs: heap fragmentation, silent integer overflow, and floating-point precision loss. This quick reference guide and FAQ provides the exact memory footprints, architectural differences, and edge-case solutions you need to write robust, crash-free firmware.

Master Data Type Matrix: AVR vs. 32-Bit Architectures

One of the most common pitfalls for makers transitioning from 8-bit boards (Uno, Nano, Mega) to 32-bit boards (ESP32, Raspberry Pi Pico RP2040, Arduino Due) is assuming data types consume the same memory. They do not. Below is the definitive matrix for standard C/C++ types as implemented in the Arduino GCC toolchains.

Data Type AVR (8-Bit) Size ESP32 / RP2040 (32-Bit) Size Value Range & Notes
boolean / bool 1 byte 1 byte 0 or 1 (false/true)
byte / uint8_t 1 byte 1 byte 0 to 255 (Unsigned)
char / int8_t 1 byte 1 byte -128 to 127 (Signed)
int 2 bytes 4 bytes AVR: -32,768 to 32,767
32-bit: -2.1B to 2.1B
unsigned int 2 bytes 4 bytes AVR: 0 to 65,535
32-bit: 0 to 4.2B
long 4 bytes 4 bytes -2,147,483,648 to 2,147,483,647
float 4 bytes 4 bytes IEEE 754 single-precision (6-7 digits)
double 4 bytes 8 bytes AVR: Same as float
32-bit: 15-digit precision
Expert Rule of Thumb: To guarantee cross-platform portability between an Arduino Nano and an ESP32, abandon generic int declarations. Instead, use explicit fixed-width types from the AVR Libc stdint library, such as int16_t, uint32_t, and int8_t.

Architectural Divergence: The 'int' and 'double' Traps

If you write a sketch using int sensorValue; on an Arduino Uno, the compiler allocates 2 bytes. If you port that exact same sketch to an ESP32-S3, the compiler allocates 4 bytes. While having more memory sounds beneficial, it can break bitwise operations or serial communication protocols that expect strict 16-bit payloads.

Similarly, the double data type is a notorious source of confusion. On 8-bit AVR boards, the Arduino Language Reference confirms that double is simply an alias for float (4 bytes, ~6 decimal digits of precision). However, on ARM and Xtensa/RISC-V architectures like the RP2040 or ESP32, double utilizes true 64-bit IEEE 754 double-precision (8 bytes, ~15 digits). If your PID control loop relies on 64-bit math, it will silently fail and lose precision when compiled for an ATmega2560.

The 'String' Class Trap: Heap Fragmentation Explained

The capital-S String object is arguably the most dangerous data type in the Arduino ecosystem when used on memory-constrained boards. Unlike a static char array, the String class dynamically allocates memory on the heap.

When you concatenate strings inside a loop (e.g., payload += sensorData;), the microcontroller must find a new, larger contiguous block of SRAM, copy the data, and free the old block. On an Uno with only 2KB of SRAM, this rapidly causes heap fragmentation. Eventually, the heap is full of unusable 'holes', and the board will hard-lock or reboot unpredictably.

  • The Fix for AVR: Use fixed-size char arrays and snprintf().
  • The Fix for ESP32: While the ESP32 has 520KB+ of SRAM and a more robust memory manager, relying on String in high-frequency IoT loops (e.g., MQTT publishing at 100Hz) will still cause long-term memory leaks. Prefer std::string or pre-allocated buffers.

Frequently Asked Questions (FAQ)

1. Why does my millis() timer fail or behave erratically after 49 days?

The millis() function returns an unsigned long (32-bit unsigned integer). The maximum value is 4,294,967,295. At 1,000 ticks per second, this maxes out at exactly 49.7 days. When it overflows, it wraps around to 0. If your timing logic uses subtraction (e.g., if (currentMillis - previousMillis >= interval)), the unsigned math naturally handles the rollover. If you use addition (e.g., if (currentMillis >= previousMillis + interval)), the logic will break catastrophically at the 49-day mark. Always use the subtraction method for timing.

2. How can I store a massive lookup table without running out of SRAM?

On an Arduino Uno, you only have 2KB of SRAM, but 32KB of Flash memory. If you define a large constant array like const int sineWave[1000] = {...};, the compiler copies it into SRAM on boot, crashing the board. You must use the PROGMEM directive to force the data to remain in Flash, and read it using pgm_read_word() or pgm_read_byte() via the standard C++ memory utilities adapted for AVR.

3. Why does sprintf() fail to format floats (%f) on the Arduino Uno?

To save precious Flash space, the AVR GCC toolchain strips out floating-point support for sprintf() and snprintf() by default. If you try to use %f, it will output a literal '?' or garbage. On AVR boards, you must use the dtostrf() function to convert floats to char arrays. Note: On 32-bit boards like the ESP32, %f works natively in snprintf(), and dtostrf() is often deprecated or missing.

4. Can I use 64-bit integers on an Arduino?

Yes. While there is no native long long alias in the classic Arduino IDE syntax, you can use the standard C++ int64_t or uint64_t types. Be aware that 8-bit AVR processors do not have native 64-bit hardware instructions; the compiler will generate dozens of assembly instructions to perform a single 64-bit addition, severely impacting execution speed. Use 64-bit types only when absolutely necessary (e.g., Unix Epoch timestamps in milliseconds).

5. What is size_t and when should I use it?

size_t is an unsigned integer type that represents the size of any object in bytes. It is the return type of the sizeof() operator and the strlen() function. On an 8-bit AVR, size_t is 16 bits (max 65,535). On a 32-bit ESP32, it is 32 bits. Using size_t for loop counters that iterate over array indices or buffer lengths ensures your code remains architecturally agnostic and prevents signed/unsigned mismatch warnings.

Pro-Tips for Flash Memory Optimization

When printing static debug strings to the Serial monitor, wrapping them in the F() macro is mandatory for AVR boards. Writing Serial.println("Sensor initialized successfully"); copies that 30-byte string into SRAM. Writing Serial.println(F("Sensor initialized successfully")); streams it directly from Flash memory, preserving your vital RAM for runtime variables and stack operations. Modern ESP32 architectures handle string literals in Flash automatically via their memory mapping unit (MMU), but using the F() macro remains a best practice for writing universally compatible Arduino libraries.