The Direct Answer: C++ with a Wiring Abstraction

When embedded hobbyists and engineers ask, "what programming language does the Arduino use?", the most accurate answer is C++. However, it is not standard desktop C++. The Arduino ecosystem utilizes a specialized dialect of C++ wrapped in the Wiring API, compiled via the GNU Compiler Collection (GCC) toolchain tailored for specific microcontroller architectures.

The Arduino IDE does not interpret a proprietary scripting language. Instead, when you click "Upload," the IDE preprocesses your .ino file by appending #include <Arduino.h> and auto-generating function prototypes. It then passes the resulting standard C++ file to an architecture-specific compiler like avr-g++ (for 8-bit AVR boards) or arm-none-eabi-g++ (for 32-bit ARM boards).

Expert Insight: The "Arduino Language" is a misnomer. Arduino is a hardware and software ecosystem. The underlying code is strictly C++, governed by the constraints of the target MCU’s memory architecture and the specific GCC version bundled with the board’s core package.

Architecture Matrix: C++ Support Across Arduino Cores (2026)

A common pitfall for developers is assuming C++ features behave identically across all boards. The C++ standard you can use depends entirely on the GCC version bundled with the board’s core package. As of 2026, the ecosystem is heavily fragmented between legacy 8-bit AVR cores and modern 32-bit RISC-V/ARM cores.

Board Family 2026 Reference Model (Price) Microcontroller GCC Toolchain Max C++ Standard SRAM Capacity
AVR (8-bit) Uno R3 Clone ($6) ATmega328P AVR-GCC 7.3.0 C++14 2 KB
ARM Cortex-M4 Uno R4 Minima ($18) Renesas RA4M1 ARM-GCC 12.2+ C++17 32 KB
RISC-V ESP32-C3 SuperMini ($4) ESP32-C3 RISC-V-GCC 12.2+ C++20 400 KB

This matrix reveals a critical best practice: Never assume modern C++20 features will compile on a standard Arduino Uno R3. The legacy arduino:avr core is locked to GCC 7.3.0, meaning features like std::string_view or structured bindings will trigger fatal compilation errors. For modern C++ patterns, you must migrate to ARM or RISC-V based boards like the ESP32-C3 or Arduino Uno R4.

Embedded C++ Code Patterns: What to Avoid and What to Adopt

Writing C++ for a desktop environment is vastly different from writing C++ for a microcontroller with 2KB of SRAM. Below are the definitive code patterns you must adopt to write robust, production-grade Arduino firmware.

Pattern 1: Banishing the String Class (Memory Fragmentation)

The most notorious anti-pattern in Arduino programming is the use of the capital-S String class. While convenient for beginners, the String class relies on dynamic heap allocation (malloc/free). On an ATmega328P with only 2048 bytes of SRAM, repeated concatenation causes severe heap fragmentation, eventually leading to silent reboots or hard crashes.

// BAD PATTERN: Heap Fragmentation Risk
String sensorData = "Temp: " + String(dht.readTemperature()) + "C";
Serial.println(sensorData);

// BEST PRACTICE: Stack-allocated character buffers
char buffer[32];
snprintf(buffer, sizeof(buffer), "Temp: %.1fC", dht.readTemperature());
Serial.println(buffer);

The Rule: Use snprintf with fixed-size char arrays allocated on the stack. If you are on an ESP32 or ARM board with ample SRAM and are using C++17, prefer std::string or fmt::format over the Arduino String class, as standard library implementations include better memory management and move semantics.

Pattern 2: Flash Memory Mapping (PROGMEM vs. const)

String literals consume precious SRAM by default. To store static data (like JSON templates, HTML payloads, or lookup tables) in Flash memory, the approach changes based on your architecture.

  • AVR Architecture (Harvard): You must use the PROGMEM keyword and the F() macro, alongside specialized read functions like pgm_read_byte() or Serial.print(F("text")).
  • ARM/RISC-V Architecture (Von Neumann / Memory-Mapped Flash): Standard const variables are automatically placed in Flash by the linker script. The F() macro is redundant and ignored by modern ESP32 and SAMD cores.

Pattern 3: Cooperative Multitasking via Finite State Machines

The delay() function is a blocking call that halts the CPU. In professional firmware, we use non-blocking Finite State Machines (FSMs) driven by millis() or hardware timers.

enum SystemState { IDLE, HEATING, COOLING, ERROR };
SystemState currentState = IDLE;
unsigned long lastStateChange = 0;

void loop() {
  unsigned long currentMillis = millis();
  
  switch (currentState) {
    case IDLE:
      if (triggerCondition()) {
        currentState = HEATING;
        lastStateChange = currentMillis;
      }
      break;
      
    case HEATING:
      if (currentMillis - lastStateChange >= 5000) { // 5 second timeout
        currentState = COOLING;
        lastStateChange = currentMillis;
      }
      break;
      
    // Handle other states...
  }
  
  // Background tasks (e.g., reading sensors, updating displays) run every loop iteration
  updateTelemetry();
}

Modern C++ Features You Can Actually Use on Microcontrollers

If you are developing on modern boards (like the $4 ESP32-C3 or $18 Arduino Uno R4), you have access to powerful C++17 and C++20 features that drastically improve code safety and optimization. According to the GCC Online Documentation, modern embedded compilers excel at optimizing high-level abstractions into bare-metal efficiency.

1. constexpr for Zero-Cost Lookup Tables

Instead of calculating complex math at runtime (which wastes CPU cycles) or storing large arrays in RAM, use constexpr to force the compiler to compute the data at compile-time and store it in Flash.

// Computed at compile time, stored in Flash, zero RAM cost
constexpr float generateSineTable(int index) {
    return 127.5 + 127.5 * __builtin_sin(index * 3.14159 / 128.0);
}

constexpr float sineTable[256] = {
    generateSineTable(0), generateSineTable(1) /* ... */
};

2. static_assert for Hardware Pin Validation

Prevent runtime hardware conflicts by validating pin assignments during compilation. This is a massive time-saver for custom PCB designs.

const uint8_t SPI_MOSI_PIN = 11;
const uint8_t SPI_MISO_PIN = 12;

// Fails compilation if pins are accidentally assigned to the same net
static_assert(SPI_MOSI_PIN != SPI_MISO_PIN, "SPI Pins must be unique!");

Understanding the Arduino API vs. Standard C++ Libraries

A frequent source of confusion is why standard C++ libraries like <iostream> or <vector> sometimes fail or bloat firmware size. The Arduino ecosystem relies on AVR Libc and specialized embedded C++ libraries (like uClibc++ or custom core implementations) rather than the full GNU libstdc++.

For example, using std::cout requires linking heavy stream buffers that can easily consume 2KB+ of Flash memory—which is 6% of the total storage on an ATmega328P. The Arduino Official Language Reference explicitly recommends using Serial.print() because it is heavily optimized for the AVR and ARM UART peripherals without the overhead of C++ iostream formatting locales.

Summary: Best Practices for 2026 and Beyond

To answer the core question: the Arduino uses C++, but the dialect and available features are strictly bound by the underlying silicon and the GCC toolchain version. To write professional-grade Arduino code in 2026:

  1. Identify your toolchain: Check the platform.txt file in your board’s core folder to see the exact GCC version.
  2. Respect the RAM: Avoid dynamic allocation (new, malloc, String) on 8-bit AVR boards.
  3. Embrace modern C++: Use constexpr, static_assert, and typed enums to catch errors at compile-time rather than runtime.
  4. Ditch the delay: Implement state machines for any project requiring concurrent sensor reading and actuator control.

By treating the Arduino not as a simplified toy language, but as a powerful C++ embedded development environment, you unlock the true potential of microcontroller programming.