Stop Using delay(): Mastering Non-blocking Timing in Arduino
Stop freezing your Arduino projects. Learn how to replace blocking delay() calls with millis() to handle multiple tasks like blinking LEDs and reading sensors simultaneously.
19 Jul 2026, 08:52 UTC

The Problem with the Pause Button
\nMany Arduino projects start with the delay() function. It is intuitive: you tell the processor to wait for 1,000 milliseconds, and it does exactly that. However, delay() is a \"blocking\" function. While the timer is running, the microcontroller stops executing all other code. It cannot read a sensor, check if a button was pressed, or update a display. To the user, the device feels frozen or unresponsive.
The solution is to switch from a sequential mindset to a polling mindset using the millis() function. Instead of telling the CPU to wait, you tell it to check the clock and decide if it is time to act.
Understanding the Millis Clock
\nThe millis() function returns the number of milliseconds that have elapsed since the Arduino board began running the current program. It returns a value of type unsigned long, which is a 32-bit integer capable of holding very large numbers.
To implement non-blocking timing, you create a \"timestamp\" variable. Every time a specific task completes, you record the current time. On every subsequent loop, you subtract that timestamp from the current millis() value. If the result is greater than or equal to your desired interval, the task triggers again.
Worked Example: Concurrent LED and Button
\nIn this scenario, we want an LED to blink every 500ms, but we need the board to detect a button press instantly, regardless of where the LED is in its blink cycle.
\n// Pin Definitions\nconst int ledPin = 13;\nconst int buttonPin = 2;\n\n// Timing Variables\nunsigned long previousMillis = 0; // Stores the last time LED updated\nconst long interval = 500; // Interval at which to blink (milliseconds)\nint ledState = LOW; // Current state of the LED\n\nvoid setup() {\n pinMode(ledPin, OUTPUT);\n pinMode(buttonPin, INPUT_PULLUP);\n Serial.begin(9600);\n}\n\nvoid loop() {\n // Capture the current time\n unsigned long currentMillis = millis();\n\n // Task 1: Non-blocking LED Blink\n if (currentMillis - previousMillis >= interval) {\n previousMillis = currentMillis; // Save the last time you blinked\n ledState = (ledState == LOW) ? HIGH : LOW; // Toggle state\n digitalWrite(ledPin, ledState);\n }\n\n // Task 2: Instant Button Check\n // This runs thousands of times per second, even while the LED is \"waiting\"\n if (digitalRead(buttonPin) == LOW) {\n Serial.println(\"Button Pressed!\");\n // Small software debounce could be added here\n }\n}\n\nCritical Implementation Details
\nWhen implementing this pattern, two technical constraints are non-negotiable:
\n- \n
- Variable Types: Always use
unsigned longfor variables storingmillis()values. If you use a standardintorlong, the math will break when the counter reaches its limit. \n - The Overflow Event: The
millis()counter resets to zero after approximately 49.7 days. By using subtraction (currentMillis - previousMillis) with unsigned integers, the result remains mathematically correct even during the wrap‑around, preventing the program from crashing or hanging. \n
Trade-offs and Limitations
\nWhile millis() enables pseudo‑multitasking, it increases code complexity. A linear script is easier to read, whereas a non‑blocking script requires managing multiple state variables and timestamps. Additionally, if a single task within the loop() takes too long to execute (e.g., a heavy calculation or a slow library call), it will still \"jitter\" the timing of other tasks because the Arduino is still fundamentally single‑threaded.
Verification and Results
\nTo verify the implementation, upload the code and open the Serial Monitor. Press the button rapidly while the LED is blinking. If the \"Button Pressed!\" message appears instantly every time, the timing is non‑blocking. If there is a perceptible lag or the message only appears occasionally, a blocking function is still present in the loop.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.