Learn Arduino Tutorials Open the 3D lab

Beginner · 6 min read

Arduino button debounce — one press, one count, every time

You press a button once and your counter jumps by four. Your LED toggle flickers on and off at random. Nothing is broken; the button is bouncing. Here is what happens inside it and the ten lines of code that fix it.

An Arduino UNO wired to a push button and an LED on a breadboard, the circuit used to test button debouncing
Build it yourself in 3DFree, in your browser — no board, no wires, nothing to buy.Open the 3D lab →

Button debounce

The short answer

When a real button's metal contacts close, they bounce: they touch, spring apart and touch again several times in a few milliseconds. The Arduino is fast enough to see every bounce as a separate press. Debouncing means ignoring changes that happen too soon after the last one, usually anything within 50 ms.

if (reading != lastReading) lastChange = millis();      // it moved: restart the timer
if (millis() - lastChange > 50 && reading != state) {   // steady for 50 ms?
  state = reading;                                      // now it really changed
}

The full sketch is below. First, why it happens.

What bouncing looks like

Inside a push button, a small springy metal piece is pushed onto two contacts (see how a push button works). Metal hitting metal doesn't stop dead. It rattles, like a dropped coin, for a short time before it settles.

5 V 0 V released pressed bounce a few ms, then steady
Time
Contact bounce on a typical small buttonabout 1–10 ms
One turn of a short loop() on an UNOa few microseconds
A fast human tapabout 100 ms

loop() can run hundreds of times during the bounce. A sketch that counts every LOW→HIGH change will count every rattle. The good news is in the last row: people press much more slowly than buttons bounce, so waiting 50 ms costs nothing you can feel.

When you need it (and when you don't)

  • You don't need it if the LED simply follows the button ("on while held"), like the push button project. A few ms of flicker at the start is invisible.
  • You do need it whenever you react to the moment of a press: counting presses, toggling on/off with one button, moving through a menu, starting a game.

The problem in code

This toggle looks right, but on a real button it often lands on the wrong state:

const int BUTTON = 2, LED = 13;
int was = HIGH;
bool on = false;

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);   // pressed = LOW
  pinMode(LED, OUTPUT);
}

void loop() {
  int now = digitalRead(BUTTON);
  if (was == HIGH && now == LOW) {   // the moment of a press
    on = !on;                        // flip
    digitalWrite(LED, on);
  }
  was = now;
}

If the contacts bounce three times, the LED flips three times: off, on, off. You pressed once, and it looks like nothing happened.

The fix: wait until it is steady

The idea: only believe a change after the pin has stayed the same for 50 ms. We use millis() so nothing else in the sketch has to wait.

const int BUTTON = 2, LED = 13;
const unsigned long DEBOUNCE = 50;    // ms

int state = HIGH;          // the button's settled state
int lastReading = HIGH;    // what we read last time
unsigned long lastChange = 0;
bool on = false;
int presses = 0;

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);      // pressed = LOW, no resistor needed
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int reading = digitalRead(BUTTON);
  if (reading != lastReading) lastChange = millis();   // it moved: restart the timer
  lastReading = reading;

  if (millis() - lastChange > DEBOUNCE && reading != state) {
    state = reading;                   // steady for 50 ms: a real change
    if (state == LOW) {                // ...and it is a press
      on = !on;
      digitalWrite(LED, on);
      presses++;
      Serial.print("presses: ");
      Serial.println(presses);
    }
  }
}

Line by line:

  • state is what we believe the button is. reading is what the pin says right now, bounces and all.
  • if (reading != lastReading) lastChange = millis(); Every rattle restarts the 50 ms timer, so a bouncing pin never gets through.
  • millis() - lastChange > DEBOUNCE is true only once the pin has been quiet for more than 50 ms.
  • reading != state means the steady value is different from what we believed, so it is a real change. We store it.
  • if (state == LOW) reacts only to the press, not the release. Each press now toggles the LED exactly once and adds exactly one to the count.
A push button on pin 2 and an LED on pin 13: the circuit this code runs on
A push button on pin 2 and an LED on pin 13: the circuit this code runs on · open it full screen

The quick fix (and why it's not the best)

Many sketches debounce with a delay:

if (was == HIGH && now == LOW) {
  on = !on;
  digitalWrite(LED, on);
  delay(50);   // ignore the bounce
}

It works for a single button in a tiny sketch. But for those 50 ms, the board is deaf to everything else: other buttons, sensors, timers. With the millis() version, the rest of loop() keeps running.

A hardware fix

You can also smooth the bounce with a capacitor: about 100 nF across the button, with the pull-up resistor. The capacitor charges and empties slowly, so the quick rattles never reach the pin. It's common in products with no spare code space, but for learning, the software fix is easier and costs nothing.

Common mistakes

  • Using an int for times. lastChange must be unsigned long, or the timer breaks after 32.7 seconds.
  • Debounce time too long. 500 ms makes fast double presses disappear. 20–50 ms is right for most buttons.
  • Forgetting lastReading = reading;. The timer restarts on every turn and the press is never accepted.
  • Counting on release and press. Without if (state == LOW) you count two per press. Pick one edge.
  • Debouncing many buttons with one timer. Each button needs its own state, lastReading and lastChange. Use arrays to keep them tidy.

Check yourself

  1. Why does one press sometimes count as several?

  2. About how long should a debounce wait be?

  3. Why use millis() instead of delay(50) to debounce?

Questions people ask

What is button debounce on Arduino?

It is code (or a small capacitor) that ignores the fast on-off flickers a button makes when its contacts touch. It makes one press count as exactly one press.

Do I need debounce with INPUT_PULLUP?

Yes. INPUT_PULLUP stops the pin from floating when the button is open, but it doesn't stop the contacts from bouncing when you press. They solve different problems.

Is there an Arduino library for debounce?

Yes, several, such as Bounce2, and they work well. Writing it yourself first, as above, shows you exactly what they do, and it is only a few lines.

Do touch sensors and keypads bounce too?

Mechanical keypads do, so they are debounced the same way, and most keypad libraries already do it. Capacitive touch sensors have no metal contacts, so they don't bounce, but their readings can be noisy.

What's next

Build the push button project and add this toggle, then use it on all eight keys of the mini piano.

Build it yourself in 3DFree, in your browser — no board, no wires, nothing to buy.Open the 3D lab →
See all tutorials →

Your progress stays on this device — no account, nothing sent anywhere.

Last updated 2026-11-11

Read next