# Button Debouncing: Hardware and Software Fixes

> A pressed button bounces a dozen times in milliseconds, so code counts five presses for one. Fix it with a 50ms software lockout or an RC filter in hardware.

[HTML version](https://soldr.ai/blog/button-debouncing-hardware-and-software-fixes)

23 August 2026 · Bharat Raj · [originally published on Compoden](https://compoden.com/blogs/guides/button-debouncing-hardware-and-software-fixes)

**Debouncing means filtering out the burst of rapid on-off transitions a mechanical button produces in the first few milliseconds after you press it, so your code registers one clean press instead of five.** Inside every push button, two pieces of springy metal collide, and like anything springy they bounce, making and breaking contact perhaps a dozen times over 1 to 10 milliseconds before settling. A microcontroller checking the pin thousands of times per millisecond faithfully reports every bounce as a separate press. The fix is either hardware (a capacitor that smooths the transitions) or, far more commonly, software (ignore further changes for about 50 milliseconds after the first one). Every button in every product you own is debounced somewhere; yours should be too.

## Why buttons bounce
A tactile switch closes when a metal dome or leaf spring snaps against a contact. Metal hitting metal rebounds microscopically, breaking the circuit, then closes again, rebounds again, and so on until the energy dissipates. On an oscilloscope, a single press looks like a ragged burst of spikes settling into a steady level. Your finger feels one clean click; the electrical reality is a drumroll.
Humans never notice because 5 milliseconds is far below our perception. But an Arduino Uno R3 loops fast enough to read the pin hundreds of times during the bounce. Code that says "if the pin changed, count a press" counts the drumroll. The symptom is unmistakable: a counter that jumps by 2 or 3 per press, a toggle that seems to pick a random state, a menu that skips items.

## The software fix: lockout by time
The standard cure costs nothing. When you detect a change, accept it, then refuse to accept another change until the input has been stable for a set interval. Fifty milliseconds is the traditional value: comfortably longer than any bounce, comfortably shorter than the fastest intentional double-press.
The simple version uses `millis()`. Remember the time of the last accepted change; on each new reading that differs from the current stable state, accept it only if `millis() - lastChange > 50`, then update the timestamp. This is non-blocking: unlike sprinkling `delay(50)` around, it lets the rest of the loop keep running, which matters the moment your sketch also drives displays or motors. The Bounce2 Arduino library packages exactly this logic if you would rather not hand-roll it.
A subtler point: debounce the state, not the event. Track "what is the button's settled state" and derive presses from settled transitions. Code that reacts to raw edges will always be one bounce away from a bug.

## The hardware fix: an RC filter
Sometimes you want the signal clean before it reaches the chip, for instance when the button feeds an interrupt pin or a counter IC. The classic circuit: a pull-up resistor (10kΩ) from the pin to 5V, the button from the pin to ground, and a 100nF capacitor across the button. The capacitor charges through the resistor slowly (time constant = 10kΩ × 100nF = 1ms), so the fast bounce spikes are absorbed; the pin voltage glides between levels instead of hammering.
One caveat: a gliding voltage spends time in the zone between clean HIGH and clean LOW, which ordinary digital inputs dislike. Serious designs add a Schmitt trigger, a gate (like the 74HC14) whose thresholds have built-in hysteresis, meaning the switching level going up differs from the level coming down, so a slow-moving input still produces one crisp edge. RC plus Schmitt is the gold-standard hardware debounce.

## Worked example: a reliable toggle on an Arduino Uno R3
Wire a tactile button on a 400-point breadboard from pin 2 to GND with Dupont jumper wires, using `pinMode(2, INPUT_PULLUP)` so the pin idles HIGH and reads LOW when pressed. Wire a 5mm LED with a 330Ω resistor on pin 13's row or use the onboard LED.
Logic: keep variables for the LED state, the last stable button state, and a timestamp. Each loop, read the pin. If the reading differs from the last stable state and 50ms have passed since the last accepted change, accept the new state, update the timestamp, and if the new state is LOW (a press, not a release), flip the LED. Without the 50ms guard, this toggle misbehaves within a minute of testing: some presses appear to do nothing because the LED flipped twice. With the guard, it is boringly perfect, which is the goal.

## Where this bites you
The most damaging version of this bug involves interrupts. A beginner learns `attachInterrupt`, connects a button, and increments a counter in the handler. Bounces trigger the interrupt over and over; the counter leaps unpredictably, and because interrupts fire between lines of the main code, the bug looks like memory corruption or ghost presses. Debouncing inside an interrupt handler is awkward (no delays allowed there), so the clean patterns are: timestamp-check within the handler, or let the interrupt only set a flag and do debounced processing in the main loop. Better still, ordinary polling with millis() handles button-speed events perfectly; interrupts for human button presses are usually unnecessary.
The second trap is blaming the button and buying a "better" one. Quality switches bounce less, but all mechanical contacts bounce. The fix belongs in your circuit or your code, not in the shopping cart.

## FAQ

### How long should my debounce time be?
20 to 50ms suits almost every tactile switch. Below 10ms, lively switches can still sneak a bounce through. Above 100ms, fast repeated presses start getting swallowed and the interface feels sluggish. When in doubt, 50ms.

### Do rotary encoders need debouncing too?
Mechanical encoders (the common knob modules) bounce on both channels, yes. But naive time-lockout can discard legitimate fast rotation. The robust approach reads both channels and uses the quadrature sequence, the fixed order in which the two signals change, to reject invalid transitions. Libraries like Encoder implement this correctly.

### My button sometimes triggers with nobody touching it. Is that bounce?
No. Phantom presses at rest are a floating or noisy input, not bounce; bounce only happens during an actual press or release. Check the pull-up (is the pin mode INPUT_PULLUP?), shorten the wires, and keep button cables away from motor and relay wiring.
We asked Soldr this exact question in a live guest session while writing this guide. Same diagnosis, unprompted: floating pin, not bounce — enable INPUT_PULLUP.Compoden's AI build assistant Soldr wires buttons with the pull-up configured and generates debounced reading code, so your first press counts exactly once.
