ESP32 S3 - LED - Blink Without Delay
The simplest way to blink an LED is with the delay() function, but this blocks the ESP32 S3 from doing anything else during the wait. This tutorial teaches you a better approach using the millis() function, which lets the ESP32 S3 blink an LED while staying free to handle other tasks simultaneously.
What you'll build:
- An ESP32 S3 circuit with an LED and a button wired to the board
- A non-blocking LED blink using millis() instead of delay()
- A button-state monitor that works correctly even while the LED is blinking
- A multi-task example blinking two LEDs at different intervals alongside button detection

We will run through three examples and compare the difference between them.
- ESP32 S3 blinks LED by using delay() function
- ESP32 S3 blinks LED by using millis() function
- ESP32 S3 blinks LED by using ezLED library
This method can be applied to let ESP32 S3 do several tasks at the same time. Blinking LED is just an example.
Hardware Preparation
Or you can buy the following kits:
| 1 | × | DIYables Sensor Kit (18 sensors/displays) |
Additionally, some of these links are for products from our own brand, DIYables .
Buy Note: Use the LED Module for easier wiring. It includes an integrated resistor.
Wiring Diagram
Connect the LED and button to your ESP32 S3 by following the wiring diagram below. Make sure the LED is wired through a 220 ohm resistor and that the polarity is correct before powering the board.

This image is created using Fritzing. Click to enlarge image
Safety Notes
Always place the 220 ohm resistor in series with the LED to limit current and protect both the LED and the ESP32 S3 GPIO pin from damage. The button connects one terminal to GND and the other to a GPIO pin; the sketch enables the internal pull-up resistor so no external resistor is needed for the button.
| Component | Pin | ESP32 S3 Pin |
|---|---|---|
| LED | Anode (long leg) | D7 |
| LED | Cathode (short leg) | 220Ω resistor to GND |
| Button | One terminal | D5 |
| Button | Other terminal | GND |
Let's compare the ESP32 S3 code that blinks LED with and without using delay() function
ESP32 S3 Code - With Delay
The following code blinks an LED using the blocking delay() function and also reads a button state on every loop iteration. While simple to write, this approach has a significant limitation: the ESP32 S3 cannot process button presses or any other input during the delay period, causing missed events.
Detailed Instructions
- New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
- Wire the Circuit: Connect the components according to the wiring diagram above.
- Connect Board: Plug the ESP32 S3 into your computer using a USB Type-C cable.
- Open Arduino IDE: Launch the Arduino IDE on your computer.
- Select Board: Choose ESP32 S3 and its corresponding COM port.
- Copy Code: Copy the code above and paste it into Arduino IDE.
- Upload Code: Click the Upload button in Arduino IDE to compile and upload.

- Open Serial Monitor: Open Serial Monitor from the Tools menu in Arduino IDE.

- Press the button 4 times while the LED is blinking.
- Observe the LED: The LED toggles between ON and OFF every second.
- Check Serial Monitor output:
- Pro Tip: Notice that you will NOT see all four button presses in the Serial Monitor. This is because the ESP32 S3 cannot detect input during the delay() blocking period — some presses are simply missed.
ESP32 S3 Code - Without Delay
The following code replaces delay() with millis() to track elapsed time without blocking the processor. Because the loop runs continuously, the ESP32 S3 can check the button state on every iteration even while it manages the LED blink timing independently.
Detailed Instructions
- New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
- Upload the code: Copy the code above and upload it to your ESP32 S3.
- Press the button 4 times while the LED is blinking.
- Observe the LED: The LED still toggles between ON and OFF every second.
- Check Serial Monitor output:
- Compare with delay version: All four button press events are now detected and logged correctly.
- Pro Tip: The millis() pattern is the foundation of multi-tasking on the ESP32 S3 — master it once and apply it to any timed operation throughout your projects.
Line-by-line Code Explanation
The above ESP32 S3 code contains line-by-line explanation. Please read the comments in the code!
Adding More Tasks
The following code demonstrates the full power of non-blocking programming on the ESP32 S3 by blinking two LEDs at different intervals while simultaneously monitoring a button — all without a single delay() call. Each task maintains its own timestamp variable, and the main loop cycles through all of them on every iteration so no task ever waits on another.

This image is created using Fritzing. Click to enlarge image
Application Ideas
Non-blocking timing with millis() is one of the most versatile patterns in ESP32 S3 programming, applicable far beyond simple LED blinking.
- Multi-sensor polling: Read temperature, humidity, and motion sensors at different intervals without any blocking between readings.
- Heartbeat indicator: Blink a status LED at a fixed rate while the ESP32 S3 simultaneously handles WiFi connections or MQTT messaging.
- Debounced button input: Combine millis() timing with button debouncing to reliably detect presses without interrupting other tasks.
- Timed relay control: Switch relays on and off at precise intervals for irrigation systems, lighting schedules, or industrial automation.
- Motor speed control: Manage PWM timing for motors or servos while monitoring safety sensors concurrently on the ESP32 S3.
- Data logging scheduler: Sample sensor data at regular intervals and buffer it for periodic upload over WiFi without blocking the sensor loop.
Video Tutorial
Watch the step-by-step video walkthrough for this ESP32 S3 project below.
Challenge Yourself
Once you have the non-blocking blink working on your ESP32 S3, push your understanding further with these progressively challenging exercises.
- Beginner: Change the blink interval from 1000 ms to 250 ms and observe how the LED blinks faster without affecting button detection.
- Beginner: Add a second LED and make it blink at twice the speed of the first LED using separate millis() timers.
- Intermediate: Use the button to toggle the LED blink on and off, so one press starts blinking and the next press stops it.
- Intermediate: Implement three independent millis() tasks: blink LED1 at 500 ms, blink LED2 at 300 ms, and read a button state at 50 ms intervals.
- Advanced: Replace the millis() timing logic with the ezLED library and compare code complexity and behavior on the ESP32 S3.