Non-blocking code architecture in Arduino - Time & Space Complexity
When writing non-blocking code on Arduino, we want to keep the program running smoothly without waiting too long in one place.
We ask: how does the time spent change as the program handles more tasks?
Analyze the time complexity of the following non-blocking code snippet.
unsigned long previousMillis = 0;
const long interval = 1000;
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// perform task
}
// other code runs here without delay
}
This code checks time repeatedly and runs a task only when the interval passes, without stopping the rest of the program.
Look for repeated actions in the code.
- Primary operation: The loop() function runs continuously, checking the time.
- How many times: It runs as fast as the Arduino can, many thousands of times per second.
Imagine the input as how often the task needs to run.
| Input Size (task frequency) | Approx. Operations per second |
|---|---|
| 10 times | Loop runs thousands of times, task runs 10 times |
| 100 times | Loop runs thousands of times, task runs 100 times |
| 1000 times | Loop runs thousands of times, task runs 1000 times |
Pattern observation: The loop runs very fast and often, but the task runs only when needed, so the main work grows with task frequency, not loop speed.
Time Complexity: O(n)
This means the time spent on the task grows directly with how many times the task runs, while the loop itself runs continuously without delay.
[X] Wrong: "The loop() function runs only once per task interval."
[OK] Correct: The loop() runs repeatedly and very fast; the task inside runs only when the time condition is met, so the loop keeps the program responsive.
Understanding non-blocking code shows you can write programs that stay responsive and efficient, a skill useful in many real-world embedded projects.
What if we replaced the time check with a blocking delay? How would the time complexity change?