{"id":52301,"date":"2026-11-01T10:50:54","date_gmt":"2026-11-01T10:50:54","guid":{"rendered":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/"},"modified":"2026-11-01T10:50:54","modified_gmt":"2026-11-01T10:50:54","slug":"non-blocking-timing","status":"publish","type":"siratek_tutorial","link":"https:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/","title":{"rendered":"Timing Without delay()"},"content":{"rendered":"<body><p><\/p>\n<h2>What this covers<\/h2>\n<p><code>delay()<\/code> is the first thing every Arduino tutorial teaches and the first thing every real project has to stop using. This explains why, and gives you the pattern that replaces it.<\/p>\n<p>It is the single step that separates a demonstration from something that works.<\/p>\n<h2>Before you begin<\/h2>\n<p>Two LEDs with resistors, on pins 9 and 10, and ideally a push button on pin 2. Everything here can be followed with one LED, but the point lands harder with two.<\/p>\n<h2>The problem<\/h2>\n<p><code>delay(1000)<\/code> does not schedule anything. The chip stops and counts. For that whole second it cannot read a sensor, notice a button, update a display or answer a serial command.<\/p>\n<p>In a blink sketch that costs nothing. In the traffic light, it means a pedestrian button pressed during the green phase is not ignored on purpose, it is never seen at all. The press happened while the program was asleep.<\/p>\n<p>The symptom is always the same: the project works, but it responds late or misses things, and the delay is exactly as long as the delay you wrote.<\/p>\n<figure class=\"siratek-tutorial-figure\"><img decoding=\"async\" src=\"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg\" alt=\"Two timelines: delay leaving the sketch blocked and missing a button press, and the millis pattern noticing it on the next pass\" loading=\"lazy\"><figcaption>Nothing is faster. The difference is that the waiting is not done by standing still.<\/figcaption><\/figure>\n<h2>The idea<\/h2>\n<p>Instead of stopping, the program keeps running and checks the time each pass: has enough time gone by to do the next thing?<\/p>\n<p><code>millis()<\/code> returns the number of milliseconds since the board started. It does not stop anything.<\/p>\n<pre><code>unsigned long now = millis();\n\nif (now - lastChange &gt;= interval) {\n  lastChange = now;\n  \/\/ Time to act.\n}<\/code><\/pre>\n<p>That is the whole pattern. <code>loop()<\/code> runs thousands of times a second, almost always doing nothing but checking, and acts on the pass where enough time has gone by.<\/p>\n<h2>Minimal working example<\/h2>\n<p>Two LEDs, different rates, from one loop. With <code>delay()<\/code> this is awkward. Here it is just two copies of the same four lines.<\/p>\n<pre><code>\/\/ Siratek: two LEDs blinking at different rates, without delay().\n\nconst int ledA = 9;\nconst int ledB = 10;\n\nconst unsigned long intervalA = 250;\nconst unsigned long intervalB = 1000;\n\nunsigned long lastA = 0;\nunsigned long lastB = 0;\n\nbool stateA = false;\nbool stateB = false;\n\nvoid setup() {\n  pinMode(ledA, OUTPUT);\n  pinMode(ledB, OUTPUT);\n  Serial.begin(9600);\n  Serial.println(\"Siratek: non-blocking blink.\");\n}\n\nvoid loop() {\n  unsigned long now = millis();   \/\/ Read the clock once per pass.\n\n  if (now - lastA &gt;= intervalA) {\n    lastA = now;\n    stateA = !stateA;\n    digitalWrite(ledA, stateA);\n  }\n\n  if (now - lastB &gt;= intervalB) {\n    lastB = now;\n    stateB = !stateB;\n    digitalWrite(ledB, stateB);\n  }\n\n  \/\/ The loop is free. Anything added here still runs thousands of times a second.\n}<\/code><\/pre>\n<p>Add <code>Serial.println(\"working\");<\/code> at the bottom of <code>loop()<\/code> and you will see it printing constantly between blinks. That free time is what the whole technique buys.<\/p>\n<h2><code>unsigned long<\/code>, and why it matters<\/h2>\n<p><code>millis()<\/code> returns an <code>unsigned long<\/code>. Your variables that hold it must be <code>unsigned long<\/code> too.<\/p>\n<p>An <code>int<\/code> on an Uno holds up to 32767, which <code>millis()<\/code> passes after <strong>32.7 seconds<\/strong>. A timer stored in an <code>int<\/code> works perfectly on the bench and then misbehaves once the project has been running for half a minute, which is a miserable bug to find.<\/p>\n<h3>The rollover, and why subtraction survives it<\/h3>\n<p><code>millis()<\/code> runs out of room and returns to zero after about 49.7 days.<\/p>\n<p>Written as a subtraction, the pattern keeps working anyway:<\/p>\n<pre><code>if (now - lastChange &gt;= interval)      \/\/ correct<\/code><\/pre>\n<p>Unsigned arithmetic wraps the same way the counter does, so the difference comes out right even across the rollover. Written as a comparison against a target, it does not:<\/p>\n<pre><code>if (now &gt;= lastChange + interval)      \/\/ wrong once millis() wraps<\/code><\/pre>\n<p>That version stops working for the rest of the run, 49 days in. You will not catch it in testing. Always subtract.<\/p>\n<h2>Doing it with a button<\/h2>\n<p>This is the version the traffic light needs: a cycle that runs on time while still noticing a press the moment it happens.<\/p>\n<pre><code>const int buttonPin = 2;\nconst int ledPin = 9;\n\nunsigned long lastBlink = 0;\nconst unsigned long blinkInterval = 500;\nbool ledState = false;\n\nint lastButton = HIGH;\n\nvoid setup() {\n  pinMode(buttonPin, INPUT_PULLUP);\n  pinMode(ledPin, OUTPUT);\n  Serial.begin(9600);\n}\n\nvoid loop() {\n  unsigned long now = millis();\n\n  if (now - lastBlink &gt;= blinkInterval) {\n    lastBlink = now;\n    ledState = !ledState;\n    digitalWrite(ledPin, ledState);\n  }\n\n  int button = digitalRead(buttonPin);\n  if (button == LOW &amp;&amp; lastButton == HIGH) {\n    Serial.println(\"pressed, and the blink never paused\");\n  }\n  lastButton = button;\n}<\/code><\/pre>\n<p>Press it mid-blink. The message appears immediately and the LED keeps its rhythm.<\/p>\n<h2>When <code>delay()<\/code> is still fine<\/h2>\n<p>It is not banned. A short pause at the end of a loop that only prints a reading is harmless, and clearer than the alternative. Some sensors need a brief settling pause that nothing else can use.<\/p>\n<p>The rule is about what else the project has to do. If nothing is waiting, <code>delay()<\/code> costs nothing. As soon as something has to be noticed while you wait, it has to go.<\/p>\n<h2>Check yourself<\/h2>\n<ol>\n<li>Your timer variable is an <code>int<\/code>. What happens after 33 seconds?<\/li>\n<li>Why is <code>now - last &gt;= interval<\/code> safer than <code>now &gt;= last + interval<\/code>?<\/li>\n<li>A button is pressed during <code>delay(2000)<\/code>. Is it queued?<\/li>\n<li>What does <code>millis()<\/code> stop while it counts?<\/li>\n<\/ol>\n<p>Answers in order: the <code>int<\/code> overflows and the timing breaks, having worked perfectly until then. Subtraction wraps correctly with the counter; the addition does not, and fails after about 49 days. No, nothing records it; the press is gone. Nothing, which is the entire point.<\/p>\n<h2>Troubleshooting<\/h2>\n<table>\n<thead>\n<tr>\n<th>Symptom<\/th>\n<th>Likely cause<\/th>\n<th>Check or fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Works, then breaks after half a minute<\/td>\n<td>Timer stored in an <code>int<\/code><\/td>\n<td>Use <code>unsigned long<\/code><\/td>\n<\/tr>\n<tr>\n<td>Everything runs at once<\/td>\n<td><code>lastChange<\/code> not updated inside the <code>if<\/code><\/td>\n<td>Set it to <code>now<\/code> when you act<\/td>\n<\/tr>\n<tr>\n<td>Timing drifts slowly<\/td>\n<td><code>lastChange = millis()<\/code> called again after work<\/td>\n<td>Reuse the <code>now<\/code> read at the top<\/td>\n<\/tr>\n<tr>\n<td>Button still missed<\/td>\n<td>A <code>delay()<\/code> left elsewhere in the loop<\/td>\n<td>Search the sketch for it<\/td>\n<\/tr>\n<tr>\n<td>LED never changes<\/td>\n<td><code>stateA<\/code> not stored between passes<\/td>\n<td>It must be global, not inside <code>loop()<\/code><\/td>\n<\/tr>\n<tr>\n<td>Interval ignored<\/td>\n<td>Interval is an <code>int<\/code> and too large<\/td>\n<td>Make intervals <code>unsigned long<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Next steps<\/h2>\n<ul>\n<li><strong>Easier first:<\/strong> <em>Dimming and Speed Control with PWM<\/em>.<\/li>\n<li><strong>Next:<\/strong> <em>Making a Button Behave<\/em>, which uses this pattern to debounce a switch properly.<\/li>\n<li><strong>Then:<\/strong> go back to the traffic light and give it a pedestrian button.<\/li>\n<\/ul>\n<h2>Sources and attribution<\/h2>\n<p>All text and code in this tutorial are original Siratek work.<\/p>\n<ul>\n<li>Arduino core documentation for <code>millis()<\/code> and its return type.<\/li>\n<li>ATmega328P datasheet for the timer behind it.<\/li>\n<\/ul>\n<p>No third-party tutorial text, diagrams, photographs or code has been reproduced here.<\/p>\n<p><\/p>\n<\/body>","protected":false},"excerpt":{"rendered":"<p>delay() is the first thing every Arduino tutorial teaches and the first thing every real project has to stop using. This explains why, and gives you the pattern that replaces it.<\/p>\n","protected":false},"featured_media":0,"template":"","siratek_tutorial_level":[],"class_list":["post-52301","siratek_tutorial","type-siratek_tutorial","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Timing Without delay() - siratek<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Timing Without delay() - siratek\" \/>\n<meta property=\"og:description\" content=\"delay() is the first thing every Arduino tutorial teaches and the first thing every real project has to stop using. This explains why, and gives you the pattern that replaces it.\" \/>\n<meta property=\"og:url\" content=\"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/\" \/>\n<meta property=\"og:site_name\" content=\"siratek\" \/>\n<meta property=\"og:image\" content=\"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/\",\"url\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/\",\"name\":\"Timing Without delay() - siratek\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/siratek.net\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/#primaryimage\"},\"image\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/non-blocking-timing-delay-vs-millis.svg\",\"datePublished\":\"2026-11-01T10:50:54+00:00\",\"breadcrumb\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/#primaryimage\",\"url\":\"https:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/non-blocking-timing-delay-vs-millis.svg\",\"contentUrl\":\"https:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/non-blocking-timing-delay-vs-millis.svg\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/non-blocking-timing\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/siratek.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tutorials\",\"item\":\"https:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Timing Without delay()\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/siratek.net\\\/#website\",\"url\":\"https:\\\/\\\/siratek.net\\\/\",\"name\":\"siratek\",\"description\":\"Your Best Electronic Retail Shop\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/siratek.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Timing Without delay() - siratek","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/","og_locale":"en_US","og_type":"article","og_title":"Timing Without delay() - siratek","og_description":"delay() is the first thing every Arduino tutorial teaches and the first thing every real project has to stop using. This explains why, and gives you the pattern that replaces it.","og_url":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/","og_site_name":"siratek","og_image":[{"url":"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/","url":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/","name":"Timing Without delay() - siratek","isPartOf":{"@id":"https:\/\/siratek.net\/#website"},"primaryImageOfPage":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/#primaryimage"},"image":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/#primaryimage"},"thumbnailUrl":"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg","datePublished":"2026-11-01T10:50:54+00:00","breadcrumb":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/#primaryimage","url":"https:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg","contentUrl":"https:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/non-blocking-timing-delay-vs-millis.svg"},{"@type":"BreadcrumbList","@id":"http:\/\/siratek.net\/index.php\/tutorials\/non-blocking-timing\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/siratek.net\/"},{"@type":"ListItem","position":2,"name":"Tutorials","item":"https:\/\/siratek.net\/index.php\/tutorials\/"},{"@type":"ListItem","position":3,"name":"Timing Without delay()"}]},{"@type":"WebSite","@id":"https:\/\/siratek.net\/#website","url":"https:\/\/siratek.net\/","name":"siratek","description":"Your Best Electronic Retail Shop","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/siratek.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial\/52301","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial"}],"about":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/types\/siratek_tutorial"}],"version-history":[{"count":0,"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial\/52301\/revisions"}],"wp:attachment":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/media?parent=52301"}],"wp:term":[{"taxonomy":"siratek_tutorial_level","embeddable":true,"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial_level?post=52301"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}