What Arduino Actually Compiles
Arduino uses a programming language based on C++. The Arduino IDE is essentially a wrapper around avr-gcc, the cross-compiler toolchain that targets Atmel AVR microcontrollers. When you write a sketch and click upload, the IDE is quietly translating your code into C++ headers, linking them with the Arduino core library, compiling everything to a binary ELF file, then stripping and flashing it to the chip via the bootloader. This is why the syntax looks like C. The setup() and loop() functions are just two conventions the Arduino core expects. You can include standard C++ headers, define structs, use classes, write templates. The language isn't restricted to any Arduino-specific subset past the API wrapper.
Arduino Uses Which Language for Different Boards
The answer changes depending on which board you are talking about. Classic Arduino boards using ATmega chips use C++ targeting the AVR architecture. ESP32 boards use C++ targeting Xtensa or RISC-V depending on the variant. ARM-based boards like the Arduino Zero or Due target the ARM Cortex-M architecture. The language remains C++ in every case, but the compiler toolchain and the underlying microcontroller architecture are different. There is also a Python-based option called MicroPython that runs on certain boards like the ESP32 and RP2040. CircuitPython exists for a handful of boards too. These are not what the standard Arduino ecosystem runs on by default, but they are available through the board manager or manual firmware flashing. Here is something most beginners miss. The Arduino language is not a separate language at all. It is C++ with a set of convenience functions baked into the core. digitalWrite(), analogRead(), Serial.print() — these are just wrapper functions that manipulate hardware registers behind the scenes. When you write pinMode(13, OUTPUT), the compiler is generating actual register writes to the DDRB and PORTB registers on the chip. This matters because it means you can bypass the abstraction entirely if you need performance or memory optimization.
I learned this the hard way a few years ago when I was building a real-time audio visualizer for an Arduino Mega. The project used a bunch of delay() calls for timing and Serial prints for debugging, and the audio input was glitching badly. The problem was that delay() blocks the entire CPU and the analog-to-digital converter was being starved of processing time. I replaced the blocking delays with a non-blocking millis() state machine pattern, removed all the Serial output from the audio-critical path, and switched to direct port manipulation for the pin writes instead of digitalWrite(). The glitching stopped. Memory usage dropped from about 85 percent free to roughly 60 percent free because I also had to add buffers for the audio data, but the timing was clean enough for the project to work reliably. Another common pitfall involves string handling. The Arduino core's String class (capital S) uses dynamic heap allocation, which fragments memory on constrained boards like the Uno with only 2KB of RAM. I once spent half a day debugging intermittent crashes in a sensor logging project. The logs would run for about 40 minutes and then the board would hang. The root cause was heap fragmentation from repeated String concatenation in a loop. The fix was switching to char arrays and snprintf() for all output formatting. After that change, the board logged continuously for weeks without issues. One more thing worth noting. If you are targeting resource-constrained boards, you should know that the Arduino core itself adds roughly 7 to 12KB of flash overhead depending on the board and which libraries are linked. On an Uno with 32KB total flash, that is a significant portion. The same project compiled for an ESP32 with 4MB of flash is nearly invisible. This is a real bottleneck if you are packing a lot of functionality onto a classic AVR board. In those cases, using PlatformIO with direct AVR-GCC commands instead of the Arduino IDE gives you finer control over linker flags and can sometimes reclaim a few kilobytes by stripping unused symbols.
Get the Full Details
The Arduino environment is deliberately simplified. That simplification is both its greatest strength and its most obvious limitation. For learning, rapid prototyping, and educational projects, it works well. When you need tight timing, minimal memory footprint, or fine-grained control over peripherals, you are working against the abstractions the platform provides. The workaround is usually just dropping down to register-level code for the hot path and keeping the Arduino wrappers for the rest.