The if...else statement in Arduino C++ executes specific code blocks based on boolean conditions. However, when you move from blinking LEDs to controlling physical hardware like relays, contactors, or motors, a naive if/else threshold check will destroy your components through rapid switching (chatter). To build robust embedded systems, you must combine the if else statement in Arduino with state-tracking variables and hysteresis bands.

In this guide, we will build a dual-threshold temperature controller using an Arduino Nano V3 and a DHT22 sensor. We will cover the exact hardware, the compilable code with error handling, and a debugging framework for when your conditional logic fails on the bench.

Why Simple If/Else Logic Fails in Physical Projects

If you write if (temp > 25.0) { relayOn(); } else { relayOff(); }, you are creating a mechanical nightmare. If the ambient temperature hovers at 25.0°C, sensor noise (often ±0.2°C on cheap thermistors) will cause the Arduino to evaluate the condition as true, then false, dozens of times per second. This arcs your relay contacts, welds them shut, or burns out the optocoupler LED.

The solution is hysteresis: introducing a deadband between your turn-on and turn-off thresholds, tracked by a state variable. Instead of asking "Is it hot?", the logic asks "Is it hot enough to turn on, given its current state?"

Bench Rule: Never use a single threshold for mechanical actuators. Always use an upper threshold to engage and a lower threshold to disengage, separated by at least the sensor's noise margin (usually 1.0°C to 2.0°C for environmental sensors).

Hardware BOM and Pin Mapping for the Nano V3

This build targets the Arduino Nano V3.0 (ATmega328P variant). Do not use the older ATmega168 variant, as it lacks the flash memory for robust string handling and some modern sensor libraries. The total BOM cost in 2026 is roughly $14.

Component Exact Variant / Spec Est. Price Notes
Microcontroller Arduino Nano V3.0 (ATmega328P, 16MHz) $6.00 Ensure it has the CH340 or FT232RL USB chip for driver compatibility.
Sensor DHT22 / AM2302 (Bare 4-pin module) $4.50 Superior to DHT11; reads down to 0.1°C and 0.1% RH.
Actuator 5V Relay Module (1-Channel, Optocoupler) $2.00 Must be Active-LOW triggered (standard blue modules).
Resistor 10kΩ (1/4W, 5% tolerance) $0.10 Required pull-up for the DHT22 data line if not on a pre-pulled PCB.

Pin Mapping Table

Component Pin Arduino Nano Pin Configuration
DHT22 VCC 5V Power
DHT22 Data D2 INPUT_PULLUP (with external 10kΩ to 5V)
DHT22 GND GND Common Ground
Relay VCC 5V Power (draws ~70mA when active)
Relay IN D8 OUTPUT (Active-LOW logic)
Relay GND GND Common Ground

Complete Compilable Code with State-Change Handling

This code uses the Adafruit DHT Sensor Library. It implements non-blocking timing via millis() to respect the DHT22's mandatory 2-second polling interval, and uses a boolean state flag to enforce hysteresis.

#include <DHT.h>

// --- PIN DEFINITIONS ---
#define DHTPIN 2
#define DHTTYPE DHT22
#define RELAY_PIN 8

// --- THRESHOLDS (Hysteresis Band) ---
#define TEMP_HIGH 26.0  // Turn relay ON at 26.0°C
#define TEMP_LOW 24.0   // Turn relay OFF at 24.0°C

// --- TIMING ---
const unsigned long POLL_INTERVAL = 2000; // DHT22 max read rate is 0.5Hz
unsigned long lastReadTime = 0;

// --- STATE VARIABLES ---
bool relayState = false; // Tracks physical relay state to prevent redundant writes

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  
  // Initialize Relay Pin
  pinMode(RELAY_PIN, OUTPUT);
  // Active-LOW relay: HIGH means OFF, LOW means ON
  digitalWrite(RELAY_PIN, HIGH); 
  
  // Initialize Sensor
  dht.begin();
  Serial.println("System Initialized. Monitoring temperature...");
}

void loop() {
  unsigned long currentMillis = millis();

  // Non-blocking delay for sensor polling
  if (currentMillis - lastReadTime >= POLL_INTERVAL) {
    lastReadTime = currentMillis;
    
    float temp = dht.readTemperature();

    // Error Handling: Check if sensor read failed
    if (isnan(temp)) {
      Serial.println("[ERROR] Failed to read from DHT22! Check wiring and pull-up.");
      return; // Exit loop iteration early, do not actuate relay on bad data
    }

    Serial.print("Current Temp: ");
    Serial.print(temp);
    Serial.println(" °C");

    // --- HYSTERESIS IF/ELSE LOGIC ---
    if (temp >= TEMP_HIGH && relayState == false) {
      relayState = true;
      digitalWrite(RELAY_PIN, LOW); // Active LOW: Trigger relay
      Serial.println("[ACTION] Threshold exceeded. Relay ENGAGED.");
    } 
    else if (temp <= TEMP_LOW && relayState == true) {
      relayState = false;
      digitalWrite(RELAY_PIN, HIGH); // Active LOW: Release relay
      Serial.println("[ACTION] Temp dropped. Relay DISENGAGED.");
    }
    // Implicit 'else': Temp is inside the deadband (24.0 < temp < 26.0).
    // We do nothing, maintaining the current state.
  }
}

Debugging: The First Three Things to Check When Logic Fails

When your if/else logic misbehaves, it is rarely a compiler issue and almost always a hardware-timing or syntax trap. Here is the ranked troubleshooting path.

1. The "Lvalue Required" or Silent Failure Trap

Exact Error String: error: lvalue required as left operand of assignment

Cause: You used a single equals sign = (assignment) instead of a double equals sign == (comparison) inside your if condition. For example: if (temp = 25.0). The compiler tries to assign 25.0 to temp, evaluates the result as "true" (non-zero), and permanently locks your logic into the if block.

Fix: Change to if (temp == 25.0). Better yet, use Yoda conditions (if (25.0 == temp)) which will throw a compile error if you accidentally use a single equals sign.

2. Sensor NaN (Not a Number) Ghosting

Symptom: The serial monitor prints nan and the relay never triggers, or triggers erratically.

Cause: The DHT22 requires a strict 2-second minimum between reads. If you put dht.readTemperature() directly in the loop() without a millis() gate, you are polling it 100,000 times a second. The sensor locks up and returns NaN.

Fix: Ensure your if (currentMillis - lastReadTime >= 2000) wrapper is intact. Additionally, verify the 10kΩ pull-up resistor is physically present on the DHT22 data line; the internal INPUT_PULLUP on the ATmega328P (approx 20kΩ-50kΩ) is often too weak for long wire runs.

3. Relay Chatter (Rapid Clicking)

Symptom: The relay clicks on and off rapidly when the temperature is near the threshold.

Cause: You are using a simple if (temp > 25) { on(); } else { off(); } without a state-tracking variable, or your hysteresis band (TEMP_HIGH minus TEMP_LOW) is smaller than the sensor's noise floor.

Fix: Implement the state-flag logic shown in the code block above. Widen the gap between TEMP_HIGH and TEMP_LOW to at least 2.0°C.

Mains Voltage Warning: If your relay is switching 120V/240V AC loads, ensure the relay module has adequate optocoupler isolation and physical clearance (creepage/clearance distances) between the low-voltage DC side and high-voltage AC side. Never route AC and DC wires in the same conduit or breadboard trough.

Extending the Build: Adding a Failsafe Timeout

A common flaw in basic if/else hardware projects is assuming the sensor will always return valid data. If the DHT22 data wire breaks, the code above safely ignores the NaN value. But what if the sensor gets stuck reporting a frozen value of 15.0°C while the actual room is overheating?

To extend this build, add a watchdog timeout. Track the last time the relay state changed. If the relay has been ON for more than 4 hours (14400000 ms), force it OFF and trigger a secondary alarm LED. This prevents catastrophic failure in applications like terrarium heaters or server room cooling fans.

Decision Path: Choosing the Right Conditional Structure

Do not default to nested if statements for every scenario. Use this decision matrix to select the correct structure for your embedded logic.

Condition Type Use Case Code Structure Concrete Example
Binary Action Single threshold, no mechanical wear (e.g., toggling an LED). Simple if if (button == LOW) { toggleLED(); }
Mutually Exclusive States Two distinct outcomes based on one variable (e.g., battery charging vs discharging). if / else if (voltage > 12.6) { charge(); } else { discharge(); }
Multi-Band Thresholds Categorizing a sensor reading into discrete ranges (e.g., Cold, Warm, Hot). if / else if / else if (t < 10) {...} else if (t < 20) {...} else {...}
Hysteresis / Stateful Controlling physical actuators where chatter must be prevented. if / else if with State Flags The exact code block provided in this guide.

Final Recommendation: For any project involving relays, solenoids, or motors, bypass simple if/else structures entirely. Default immediately to the Hysteresis / Stateful pattern using boolean state flags and a minimum 1.5°C to 2.0°C deadband. It requires three extra lines of code but increases the physical lifespan of your actuator by orders of magnitude.