The Core Logic: Mastering if else in Arduino
The if else statement in Arduino evaluates a boolean condition; if the condition is true, it executes the first block of code, otherwise it falls through to else if or else blocks. While the syntax is basic C++, the real trap in embedded systems isn't the grammar—it's how conditional logic interacts with hardware realities like floating inputs, sensor timeouts, and blocking delays.
When you write if (temp > 30), you aren't just making a software decision; you are closing a physical circuit, spinning a motor, or triggering a relay. If your sensor returns NaN (Not a Number) due to a loose wire, that if statement will evaluate unpredictably, potentially leaving a heater on indefinitely. This guide moves past abstract syntax and builds a robust, non-blocking temperature-controlled dual-fan system to demonstrate how to write, debug, and scale conditional logic on the bench.
if to test a primary condition, else if for secondary mutually exclusive conditions, and else as the default fallback. Always validate sensor data with isnan() before passing it into your conditional blocks to prevent hardware runaway.
Project Build: Temperature-Controlled Dual-Speed Fan
To ground this concept, we are building a smart cooling system. It reads ambient temperature and controls two 12V PC fans via relays. Below 25°C, both fans are off. Between 25°C and 29.9°C, Fan 1 runs (low speed). At 30°C and above, both fans run (high speed). A manual override button forces high speed regardless of temperature.
Parts List
- Microcontroller: Arduino Nano V3 (ATmega328P, 16MHz, 5V logic)
- Sensor: DHT22 (AM2302) Temperature and Humidity Sensor
- Actuator: 2-Channel 5V Relay Module (Optocoupler isolated, Active-LOW trigger)
- Input: 6x6mm Tactile Pushbutton Switch
- Power: 12V 2A DC Power Supply (for fans) + 5V USB (for Nano)
- Misc: Half-size solderless breadboard, 22 AWG jumper wires, two 12V 80mm PC fans
Wiring and Pin Configuration
The DHT22 requires a single data line, while the relays and button take up standard digital I/O. We use the Nano's internal pull-up resistor for the button to eliminate the need for an external 10kΩ pull-down resistor, reducing component count and wiring clutter.
| Component | Module Pin | Arduino Nano Pin | Notes |
|---|---|---|---|
| DHT22 Sensor | VCC | 5V | Requires 3.3V to 5V |
| DHT22 Sensor | DATA | D2 | Internal pull-up enabled in code |
| DHT22 Sensor | GND | GND | Common ground |
| Relay Channel 1 | IN1 | D3 | Fan 1 (Low Speed) |
| Relay Channel 2 | IN2 | D4 | Fan 2 (High Speed Add-on) |
| Relay Module | VCC / GND | 5V / GND | Optocoupler power |
| Pushbutton | Leg 1 | D5 | Configured as INPUT_PULLUP |
| Pushbutton | Leg 2 | GND | Pulls D5 LOW when pressed |
The Complete Code: Conditionals with Error Handling
This code targets the Arduino Nano V3 (ATmega328P). It uses the standard Adafruit DHT library. Crucially, it avoids putting delay() inside the conditional blocks, using millis() instead to ensure the override button is read instantly, even if the sensor takes time to respond.
#include
// --- Pin Definitions ---
#define DHTPIN 2
#define DHTTYPE DHT22
#define RELAY1_PIN 3
#define RELAY2_PIN 4
#define OVERRIDE_PIN 5
// --- Thresholds ---
const float TEMP_LOW = 25.0;
const float TEMP_HIGH = 30.0;
DHT dht(DHTPIN, DHTTYPE);
unsigned long lastReadTime = 0;
const long readInterval = 2000; // DHT22 needs 2s between reads
float currentTemp = 0.0;
bool sensorValid = false;
void setup() {
Serial.begin(115200);
// Relays are Active-LOW, so HIGH means OFF
pinMode(RELAY1_PIN, OUTPUT);
pinMode(RELAY2_PIN, OUTPUT);
digitalWrite(RELAY1_PIN, HIGH);
digitalWrite(RELAY2_PIN, HIGH);
pinMode(OVERRIDE_PIN, INPUT_PULLUP);
dht.begin();
Serial.println("System Initialized. Waiting for sensor...");
}
void loop() {
unsigned long currentMillis = millis();
// 1. Non-blocking Sensor Read
if (currentMillis - lastReadTime >= readInterval) {
lastReadTime = currentMillis;
float t = dht.readTemperature();
if (isnan(t)) {
Serial.println("ERROR: DHT22 read failed. Check data wire.");
sensorValid = false;
} else {
currentTemp = t;
sensorValid = true;
}
}
// 2. Read Hardware Inputs
bool overrideActive = (digitalRead(OVERRIDE_PIN) == LOW);
// 3. Conditional Logic Execution
if (!sensorValid) {
// FAILSAFE: If sensor is dead, shut everything down to prevent fire/runaway
digitalWrite(RELAY1_PIN, HIGH);
digitalWrite(RELAY2_PIN, HIGH);
} else if (overrideActive) {
// MANUAL OVERRIDE: Force High Speed (Both Fans)
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, LOW);
} else if (currentTemp >= TEMP_HIGH) {
// AUTO HIGH: Temp >= 30C, Both Fans
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, LOW);
} else if (currentTemp >= TEMP_LOW) {
// AUTO LOW: 25C <= Temp < 30C, Fan 1 Only
digitalWrite(RELAY1_PIN, LOW);
digitalWrite(RELAY2_PIN, HIGH);
} else {
// AUTO OFF: Temp < 25C, No Fans
digitalWrite(RELAY1_PIN, HIGH);
digitalWrite(RELAY2_PIN, HIGH);
}
}
Debugging: When Your if else Logic Fails
When your hardware doesn't respond the way your logic dictates, the issue usually falls into two categories: compiler errors (syntax) or runtime failures (hardware/logic interaction).
Exact Compiler Error Strings
If your code won't compile, look for these exact strings in the Arduino IDE console:
error: 'else' without a previous 'if'
Cause: You placed a stray semicolon at the end of yourifcondition. Example:if (temp > 25); { ... }. The compiler sees the semicolon as the end of theifstatement, making the bracketed code an independent block, leaving theelseorphaned.
Fix: Remove the semicolon immediately following the closing parenthesis of theifcondition.error: expected '}' at end of input
Cause: You forgot to close a bracket for anifblock, or you missed the final closing bracket for thevoid loop()function.
Fix: Use the IDE's auto-format tool (Ctrl+T / Cmd+T). The indentation will reveal exactly which block is left hanging.
The First 3 Things to Check for Runtime Failures
If the code compiles and uploads, but the relays click erratically or ignore the button, check these three hardware-logic traps:
- Floating Inputs: If you used
pinMode(OVERRIDE_PIN, INPUT)without an external pull-down resistor, the pin will read random electromagnetic noise, triggering theif (overrideActive)block randomly. Fix: Always useINPUT_PULLUPand wire the button to GND. - Sensor Timeouts (NaN): The DHT22 relies on strict timing. If the 5V rail sags or the data wire is too long (>2 meters), it returns
NaN. If you passNaNintoif (currentTemp >= 30), C++ evaluates it as false, but it breaks the logical flow. Fix: Always use theisnan()check as your very firstifcondition to trigger a failsafe. - Blocking Delays Inside Conditions: If you put
delay(5000)inside theif (currentTemp >= 30)block to "keep the fan on for 5 seconds", the microcontroller stops reading the override button for 5 full seconds. Fix: Use themillis()state-machine approach shown in the code above.
Extending and Simplifying the Build
Once the basic if else logic is stable, you can scale the project up or strip it down based on your enclosure constraints.
- Extend with Hysteresis: Right now, if the temperature hovers at exactly 25.0°C, sensor noise might cause the relay to chatter (click on and off rapidly). Extend the logic by adding a 1°C deadband. Turn the fan on at 25.5°C, but don't turn it off until it drops to 24.5°C. This requires storing the previous relay state in a boolean variable.
- Extend with PWM: Instead of relays, swap to logic-level MOSFETs (like the IRLZ44N) and use
analogWrite(). You can replace the rigidif elsethresholds with a proportional mapping function, scaling the fan speed smoothly from 0 to 255 based on the exact temperature delta. - Simplify with a State Machine: If you add humidity control, an LCD screen, and WiFi, nested
if elsestatements become a spaghetti-code nightmare. Simplify by moving to aswitch casestructure driven by anenumstate variable (e.g.,STATE_IDLE,STATE_COOLING,STATE_OVERRIDE).
Frequently Asked Questions
Can I nest an if else inside another if statement in Arduino?
Yes, you can nest them infinitely, but it is highly discouraged in embedded programming. Deeply nested conditionals (more than 2 levels deep) consume extra stack memory and make debugging hardware faults nearly impossible. If you find yourself nesting if statements to check multiple sensor states, refactor your code to use logical operators (&& for AND, || for OR) or switch to a switch case state machine.
Why is my Arduino else statement executing when the if condition is true?
This almost always happens due to a stray semicolon immediately after the if condition. For example: if (buttonState == HIGH); { digitalWrite(LED, HIGH); }. The compiler interprets the semicolon as an empty command, effectively closing the if statement. The code inside the brackets then runs unconditionally, and a subsequent else block will trigger because the compiler thinks the if block was empty. Remove the semicolon.
What is the difference between if else and switch case in Arduino?
Use if else when evaluating ranges, floating-point numbers, or complex boolean logic (e.g., if (temp > 25.5 && humidity < 40)). Use switch case when evaluating a single integer or character variable against specific, discrete values (e.g., parsing serial commands or managing UI menu states). switch case is computationally faster and cleaner for discrete states, but it cannot evaluate ranges like >= 30.
How do I write an if else statement with multiple conditions?
Combine conditions using the logical AND (&&) or logical OR (||) operators. For example, to turn on a heater only if it is cold AND the manual override is off: if (temp < 18.0 && override == false). Always use parentheses to group complex logic to ensure the compiler evaluates it in the order you intend, preventing subtle logic bugs caused by C++ operator precedence rules.
For official syntax documentation, refer to the Arduino Language Reference for if/else. For sensor wiring specifics, consult the SparkFun DHT22 Hookup Guide.






