If one physical button press triggers your Arduino or microcontroller several times, the problem is usually not your wiring or processor. It is mechanical switch bounce. When I need to learn how to debounce a push button in code, I focus on filtering short, unstable signal changes before allowing the program to react.
For most projects, I prefer a non-blocking software debounce method using millis(). It keeps the microcontroller responsive while still rejecting false button transitions. That makes it especially useful for Arduino projects that also control LEDs, motors, displays, sensors, or communication modules.
Table of Contents
ToggleWhat Causes a Push Button to Register Multiple Presses?
A mechanical push button contains metal contacts that physically touch when you press it. Those contacts do not always move cleanly from open to closed.
Instead, they can make and break contact several times over a few milliseconds before settling.
The ideal signal might look like this:
HIGH → LOW
But the real electrical signal may briefly look more like this:
HIGH → LOW → HIGH → LOW → HIGH → LOW
A microcontroller reads inputs very quickly, so it may interpret those rapid changes as multiple presses.
Software debounce solves this by waiting until the input stays stable long enough to confirm that the change is real.
How Does Software Button Debouncing Work?
The basic idea is simple: read the button continuously, watch for changes, start a timer when a change appears, and only accept the new state after it remains stable for a specified period.
This approach is much more reliable than immediately triggering an action whenever digitalRead() changes.
How Does the Raw Button Reading Work?
During every pass through loop(), the microcontroller reads the instantaneous state of the input pin.
int reading = digitalRead(buttonPin);
This raw reading may represent a genuine press, a release, or a temporary bounce.
Because of that, I never use the raw input alone to trigger the final action.
How Does State Change Detection Reset the Debounce Timer?

The next step compares the latest raw reading with the previous reading.
if (reading != lastReading) {
lastDebounceTime = millis();
}
A change does not automatically mean the contact is bouncing. It could be the beginning of a legitimate button press or release.
However, the signal has not yet proven that it is stable, so the code resets the debounce timer.
If the signal changes a few milliseconds again later, the timer resets again.
How Does the Debounce Threshold Check Work?
The code then measures how much time has passed since the most recent state change.
if ((millis() – lastDebounceTime) >= debounceDelay) {
// Signal has remained stable long enough
}
For many standard mechanical push buttons, a debounce interval between about 20 and 50 milliseconds is a practical starting range.
A 50 ms threshold is common in example code because it gives most switches plenty of time to settle. I often start around 30 ms and adjust based on the actual hardware.
How Does the Code Confirm a Stable Button State?
Once the input stays unchanged longer than the debounce period, the program treats the reading as valid.
if (reading != stableState) {
stableState = reading;
}
At that point, the code can safely trigger the appropriate action.
This stable-state confirmation is what prevents a short burst of electrical bouncing from being interpreted as multiple presses.
Complete Arduino Push Button Debounce Code

Here is a clean non-blocking example:
const int buttonPin = 2;
int stableState = HIGH;
int lastReading = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 30;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
int reading = digitalRead(buttonPin);
if (reading != lastReading) {
lastDebounceTime = millis();
}
if ((millis() – lastDebounceTime) >= debounceDelay) {
if (reading != stableState) {
stableState = reading;
if (stableState == LOW) {
Serial.println(“Button pressed”);
} else {
Serial.println(“Button released”);
}
}
}
lastReading = reading;
}
This code handles both the debounce process and button edge detection, allowing the program to distinguish between a new press and a release.
Why Should I Use INPUT_PULLUP for a Push Button?
For a simple Arduino button circuit, I usually connect one side of the button to a digital input pin and the other side to ground.
Then I enable the internal pull-up resistor:
pinMode(buttonPin, INPUT_PULLUP);
This keeps the input from floating when the button is open.
With INPUT_PULLUP, the logic is reversed. A released button reads HIGH, while a pressed button reads LOW.
That inversion often confuses beginners, but it reduces component count and works well for many basic projects.
Why Is millis() Better Than delay() for Debouncing?
A simple debounce example might use:
delay(50);
That can work in a small experiment, but delay() pauses the main program.
During that pause, the microcontroller cannot efficiently handle other tasks inside the loop.
A millis() debounce is non-blocking. The program can continue controlling motors, checking sensors, updating a display, or monitoring additional buttons while the debounce timer runs.
For more complex embedded projects, that difference matters.
How Long Should a Push Button Debounce Delay Be?

There is no universal value that works for every switch.
Around 20 to 50 milliseconds is a useful starting range for many standard mechanical push buttons. A noisy or inexpensive switch may need slightly more time, while a higher-quality switch may work with less.
If the value is too short, false triggers can still appear.
If the value is unnecessarily long, rapid button presses may feel less responsive.
The best approach is to test the physical switch used in the actual project.
How Do I Debounce Multiple Buttons?
Multiple-button debounce uses the same logic, but each button needs its own raw reading, stable state, previous reading, and debounce timer. For two buttons, separate variables may be manageable. For a larger control panel, I prefer wrapping the debounce logic inside a function, structure, or class.
This approach also works well when following the best breadboard layout for beginner circuits, because keeping each button connection organized makes both wiring and debugging easier. That makes the same debounce algorithm reusable and prevents the code from becoming difficult to maintain.
What Are the Most Common Button Debouncing Mistakes?
A floating input is one of the most common problems. If the pin has no pull-up or pull-down resistor, it may randomly switch between HIGH and LOW.
Another common issue is forgetting that INPUT_PULLUP makes LOW mean pressed.
It is also easy to trigger an action continuously while the button remains held. In most cases, the code should respond only when the stable state changes.
Finally, an unnecessarily large delay() can make the entire program sluggish. That is why I prefer timer-based debounce logic in most real projects.
Frequently Asked Questions (FAQs)
1. What is the best way to debounce an Arduino push button?
A non-blocking millis() approach is one of the most practical methods because it confirms that the input has remained stable without pausing the rest of the program.
2. Is 50 ms enough for button debouncing?
For many mechanical buttons, yes. However, the ideal debounce delay depends on the switch, wiring, electrical noise, and application.
3. Can I detect both button press and release with debounce code?
Yes. Once the stable state changes, you can test whether it became LOW or HIGH and handle the press and release separately.
4. Do I need hardware components for button debounce?
Not always. Software debounce often works well by itself. Hardware RC filters or Schmitt-trigger circuits are useful when the environment is electrically noisy, or the signal must be cleaned before reaching the processor.
Final Thoughts
Once I understand how to debounce a push button in code, I can eliminate false triggers without adding unnecessary complexity. The most reliable pattern is to read the raw input, detect changes, reset a timer, check the debounce threshold, and update the stable state only after the signal settles.
For small experiments, a short delay may work. For responsive Arduino and embedded projects, I recommend a millis()-based approach because it keeps the program free to handle other tasks while still producing clean button input.
If you are building anything from a basic LED controller to a robot or control panel, understanding how to debounce a push button in code gives you a simple way to make button input much more reliable.



