Skip to content

Embedded Systems

How to blink an LED without delay() on Arduino

To blink an LED without delay(), don't wait for the time to pass: glance at the clock. Each pass of loop(), check whether millis(), the milliseconds since the board started, is at least one interval past the last flip. If it is, flip the LED and note the time. If not, carry on. loop() never stops, so it can do other jobs between flips.

The whole sketch

const unsigned long interval = 500;  // ms between flips
unsigned long since = 0;             // when the LED last flipped
bool on = false;

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  if (millis() - since >= interval) {
    since = since + interval;
    on = !on;
    digitalWrite(9, on ? HIGH : LOW);
  }
  // other jobs go here, and run on every pass
}

Line by line

  • since remembers when the LED last flipped. It's an unsigned long because millis() grows large.
  • millis() - since is how long it's been. Elapsed time is always now minus then.
  • >= interval asks whether it's time yet. Most passes, the answer is no and the block is skipped.
  • since = since + interval moves the note on by exactly one interval, ready for the next flip.
  • on = !on flips a bool, and digitalWrite puts it on the pin.

Without a delay() in it, this loop() comes round thousands of times a second. A blink every half second takes a tiny share of that.

Live experiment

Glance at the clock, don't wait

The bar is millis() − since filling up toward the interval. When it gets there, the LED flips and since moves on by one interval. Change the interval, or freeze the clock to read the numbers mid-glance.

Try it yourself: free lesson

“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 before it first.

The bugs people hit

Setting since = millis() instead of adding the interval works, but the check always runs a little late, and that lateness adds up: the blink slowly drifts. Adding the interval keeps the rhythm exact.

Comparing millis() with a future deadline, like millis() >= next, breaks when millis() rolls over to 0 after about 49.7 days. The subtraction millis() − since survives the rollover, as long as both are unsigned.

Why bother

A blink with delay(500) twice can only read a button once a second. Here the button, a sensor and the blink all get checked on every pass. Two LEDs at different rates are just two since variables and two intervals: each job decides for itself when it's due.

Free lesson

Now build it yourself

Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.

Try it yourself: free lesson

“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 before it first.

Or start from the very first lesson, no account needed

Practice questions

Three quick questions from the lessons. Pick an answer to check it.

  1. Question 1 of 3While delay(500) runs, the board…
    Show the answer

    Answer: Does nothing else. delay() stops the program on that line until the time is up. Nothing else runs, so a press in that half second goes unnoticed.

    • Why not “Runs loop() twice”? loop() can't start another pass while it's stuck inside delay(); the wait has to end first.
    • Why not “Keeps reading the button”? delay() holds the program on that one line, so nothing reads the button until the wait is over.
    • Why not “Blinks faster”? The blink speed is set by the waits themselves. delay() makes nothing faster; it only stops the board for a while.
  2. Question 2 of 3The last blink was at millis() = 4200. It's now 4650. Has 500 ms passed?
    Show the answer

    Answer: No: only 450 ms. 4650 − 4200 = 450. Not yet: check again next time round the loop.

    • Why not “Yes”? 4650 − 4200 is 450 ms, still 50 ms short of the 500 the blink is waiting for.
    • Why not “Exactly”? Exactly 500 ms would need millis() to read 4700. At 4650 the gap is only 450.
    • Why not “Can't tell”? Both times are known, so the gap is easy to find: now minus then, 4650 − 4200 = 450 ms.
  3. Question 3 of 3if (millis() - since >= 1000) { since = since + 1000; … } runs its block…
    Show the answer

    Answer: once a second. Each time 1000 ms have passed, the block runs and moves since on by 1000, so the next run waits another second.

    • Why not “never”? millis() keeps climbing, so the gap reaches 1000 after a second and the test comes true.
    • Why not “on every pass of loop()”? The block only runs once millis() − since reaches 1000. On the passes in between the test is false and it's skipped.
Keep practising: free lesson

“Tick, don't wait” is lesson 1 of 6 in Clockwork Cove, Embedded Systems World 2. Free with an account. Lessons unlock in order, so you’ll do the 7 before it first.

Questions people ask

What is blink without delay on Arduino?

A way to blink an LED that never stops the program. Instead of delay(), each pass of loop() checks whether enough time has passed since the last flip, using millis(). If it has, it flips the LED; if not, loop() carries on with its other jobs.

Why use unsigned long for millis()?

millis() returns an unsigned long, a whole number from 0 to about 4.3 billion. An int on an Uno stops at 32,767, which millis() passes after about 33 seconds, and the timing breaks.

Can I blink two LEDs at different rates without delay?

Yes. Give each LED its own since variable and its own interval, and check each one on every pass of loop(). Neither waits for the other, so each keeps its own rhythm.

Every topic