Source: https://mokxi.com/learn/arduino-millis-overflow

Learn

# Arduino millis() overflow: what happens at the wrap, and the one line that survives it

Written by the Mokxi team

Start building free

Or see every lesson.

- 455 parts on the bench
- 43 boards running now
- 1.00x real time, on every board

Click to open it in the editor

Pin 13 blinks every 250 ms and pin 12 every 400 ms, each timed with now - last >= interval.

millis() counts milliseconds since the board started, in an unsigned long. That is 32 bits on an Uno and on an ESP32 alike, so the count goes up to 4294967295 and then wraps around to 0 and carries on. It does not reset the board, it does not stop, and it does not throw an error. It simply starts again from zero, and any sketch that compares times the wrong way breaks at that moment.

The fix is one rule: always subtract. Write if (millis() - start >= interval), with start and the result as unsigned long, and the comparison is right before the wrap, across it and after it. Written as if (millis() >= start + interval) it fails at the wrap. The rest of this page shows why, with a sketch that runs across the wrap on a simulated Uno and prints every value.

The circuit above blinks two LEDs at two rates from one loop with no delay(): pin 13 every 250 ms and pin 12 every 400 ms. Its loop is built on exactly that line, once for each LED.

## How long until millis() rolls over

The largest value an unsigned long holds is 4294967295. Add one and you get 0: on the simulated Uno, Serial.println(4294967295UL + 1) printed 0. As milliseconds, 4294967296 is 49.71 days, which is the "49.7 days" every forum answer quotes. A board that runs for seven weeks meets it.

micros() uses the same 32-bit type, but counts a thousand times faster, so it wraps after 4294967296 microseconds, 71.58 minutes. That one is not a curiosity. A sketch that times things with micros() meets the wrap a little more than an hour after it starts, which is long enough to pass every test on the bench and short enough to fail on the first evening of real use.

## The wrap, run on a simulated Uno

Nobody wants to wait 49.7 days to see a bug, in a simulation or on a desk. So this sketch keeps its own clock: it starts it 296 ms before the wrap, at 4294967000, and steps it forward 100 ms at a time, as millis() would. At each step it asks both questions, "has 1000 ms gone by?" by subtraction and by addition, and prints the answers. The arithmetic is the board’s own: 32-bit unsigned math, run on the simulated ATmega328P.

It printed "t=0 now=4294967000 now-start=0 subtraction: wait, addition: DUE" on its first line. The addition says the interval is already over before a single millisecond has passed. That is because start + interval is 4294968000, which does not fit, so it wrapped to 704, and every value of now before the wrap is bigger than 704.

The subtraction kept counting: 100, 200, 300 straight across the wrap, where now went from 4294967200 to 4, and reported "due" for the first time on the line "t=1000 now=704 now-start=1000 subtraction: DUE, addition: DUE". That is exactly 1000 ms after the start. The addition, meanwhile, said "DUE" until the wrap and "wait" after it, right only by accident from the 1000 ms line on. Away from the wrap both agree, which is why the addition version passes every short test.

Both comparisons, stepped across the wrap

## Why the subtraction is always right

Unsigned arithmetic in C and C++ is arithmetic modulo 2 to the 32nd: every result wraps the same way the clock does. When now has wrapped to 704 and start is still 4294967000, the true difference is 1000, and 704 minus 4294967000, wrapped, is 1000. The two wraps cancel. As long as the interval you are measuring is shorter than 49.7 days, now - start is the time that has really gone by.

The addition has no such luck. start + interval wraps on its own, and now wraps at a different moment, so for a while one side has wrapped and the other has not, and the comparison answers the wrong question. Checking against a stored deadline, if (millis() > deadline), is the same mistake in a different shape.

## The other ways it goes wrong: the wrong type

The subtraction only works in unsigned long. Store millis() in a signed long and it turns negative after 24.86 days, half the range: on the Uno, (long)4294967000UL printed -296. Store it in an int on an Uno, which is 16 bits there, and it breaks after 32.767 seconds: (int)40000UL printed -25536, so a sketch that saves int start = millis(); misbehaves within its first minute. On an ESP32 an int is 32 bits, which hides the second mistake but not the first.

Use unsigned long for every variable that holds a time, and for the interval too. const unsigned long INTERVAL = 1000; costs nothing and keeps the whole expression in the right type.

## Do you need to handle the overflow at all?

Not if every comparison is a subtraction in unsigned long. There is no need to detect the wrap, reset millis() or reboot the board every few weeks; sketches that do those things are working around a comparison written the wrong way. delay() already measures its wait this way, so a delay() that spans the wrap is fine.

The one real limit is an interval longer than the counter itself. You cannot measure 60 days with one millis() subtraction. Count something slower instead: add one to a days counter every time 86400000 ms have gone by, using the same subtraction, or read the time from a real-time clock module.

## Questions

What is the maximum value of millis()?

4294967295, the largest unsigned long. One millisecond later it is 0. On a simulated Uno, 4294967295UL + 1 prints 0.

How long until millis() overflows?

About 49.71 days, 4294967296 milliseconds. micros() overflows after about 71.58 minutes.

Does the Arduino reset when millis() overflows?

No. The count wraps to 0 and keeps going, and the sketch carries on. Only comparisons written as an addition, or stored in the wrong type, go wrong at that moment.

How do I avoid millis() overflow problems?

Write every check as if (millis() - start >= interval), with start and interval declared unsigned long. That one line is correct across the wrap, so there is nothing else to handle.

Related

## Keep going

Arduino Blink Without Delay: Two LEDs, Two Rates Interrupts vs Polling on Arduino, Side by Side Arduino State Machine: A Four-State Reaction Game How to Debug Arduino Code: Prints to Breakpoints Arduino Data Types: int, long, float and byte Arduino millis(): Timers and Multiple Tasks The Arduino Uno simulator The debugger, to watch now and start change

Sources

## Where the facts on this page come from

- Arduino reference: millis(), which overflows after about 50 days
- Arduino reference: micros(), which overflows after about 70 minutes

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building free Open the editor
