Electronics Design AU
Embedded SystemsDigital

How Do You Debounce a Button or Switch?

Last updated 6 July 2026 · 8 min read

Direct Answer

Mechanical switch contacts physically bounce when they close or open, producing a burst of spurious transitions that typically lasts a few milliseconds (though some switches bounce for 10–20 ms or more). Debouncing means ignoring those transitions until the contact settles, and there are two places to do it: in hardware — an RC low-pass filter feeding a Schmitt-trigger input, an SR latch on a changeover (SPDT) switch, or a dedicated debouncer IC — or in firmware, by accepting a new state only after it has been stable for a defined window (commonly 10–50 ms), implemented with periodic sampling and a counter, or an edge interrupt that starts a timer. Firmware debouncing is free in components and is the default choice for ordinary user buttons; hardware debouncing is the right answer when the signal must be clean before the MCU sees it — wake-from-sleep pins, hardware counter/clock inputs, or safety-related inputs.

Detailed Explanation

Every mechanical switch — tactile pushbuttons, toggle switches, relay contacts, limit switches — closes and opens by physically slamming two metal contacts together. The contacts strike, rebound microscopically, strike again, and scrape before settling, so instead of one clean electrical transition the MCU pin sees a burst of transitions. Empirical measurements across many switch types (Jack Ganssle's widely cited study is the standard reference) show most switches settle within a few milliseconds, with some bouncing beyond 10–20 ms — and bounce occurs on release as well as press, often with different behaviour.

Firmware running at tens or hundreds of megahertz resolves every one of those transitions. The classic symptoms: a menu that advances two or three items per press, a counter that increments by a random amount, an interrupt that fires a dozen times per click, or a device that wakes from sleep the moment it enters it. None of these are firmware logic bugs — they're an unhandled physical property of the input, and the fix is a deliberate debounce strategy in hardware, firmware, or both.

This page covers mechanical contact bounce specifically. A capacitive touch button has no physical contacts to bounce, so it doesn't have this problem — but it introduces a different noise-filtering challenge (baseline drift, false triggering from moisture) that needs its own handling, not a debounce timer.

What Bounce Looks Like

Put an oscilloscope on the pin (single-shot trigger on the first edge) and a typical press shows: a clean first edge, then several rapid full-swing transitions over the next few hundred microseconds to a few milliseconds, then a stable level. This five-minute measurement is worth doing on the actual production switch — bounce duration varies significantly between switch types, ages with mechanical wear, and is the number every other design decision on this page depends on.

Hardware Debouncing

Hardware debouncing cleans the signal before the MCU ever sees it:

  • RC filter + Schmitt trigger. A resistor-capacitor low-pass filter (e.g. the switch pulling a 100 nF capacitor low through the existing pull-up — values vary by design) stretches the bounce burst into one slow ramp. The essential second half is a Schmitt-trigger input: an input with hysteresis (like the SN74LVC1G17 buffer, or an MCU pin whose datasheet specifies real input hysteresis), because a slow ramp through an ordinary CMOS threshold can itself cause multiple logic transitions. Size the RC time constant so the voltage can't cross the hysteresis band within the worst-case bounce time.
  • SR latch on an SPDT switch. A changeover switch plus two cross-coupled NAND gates gives a mathematically bounce-free output — the first contact touch latches the state and rebounds can't un-latch it. Elegant, but requires a three-terminal switch, so it's rare in cost-driven designs.
  • Dedicated debouncer ICs. Parts like the Analog Devices/Maxim MAX6816 family integrate the whole function (and add ±15 kV ESD protection) in a tiny package — a clean answer for switches on cables or panels where ESD protection was needed anyway.

Hardware debouncing is required, not optional, wherever the raw signal drives something that reacts to every edge with no firmware in the path: a hardware counter/timer input, a clock line, a wake-from-deep-sleep pin (the firmware isn't running yet to debounce it), or resets — which is one reason supervisor ICs provide internally debounced manual-reset inputs.

Firmware Debouncing

For ordinary buttons read by running firmware, software debouncing costs nothing in BOM and is the default. The three robust patterns:

  • Periodic sampling with a stability counter. Sample the GPIO pin every 1–10 ms (timer tick, RTOS task, or main-loop scheduler). Accept a new state only after N consecutive samples agree — e.g. 5 samples at 5 ms intervals gives a 25 ms window. Scales to many inputs (one byte of state each, or "vertical counters" to debounce a whole port in parallel), has bounded CPU cost, and is immune to interrupt storms.
  • Edge interrupt + one-shot timer. On the first edge, mask further edges for that pin and start a one-shot timer for the debounce window; when it expires, read the settled level, act on it, and re-arm the interrupt. This is the right shape when the input must work from sleep or the system is otherwise idle. (On an RTOS, the coarse-tick software timers most SDKs provide — like the FreeRTOS timers discussed in the ESP32 GPIO and timers guide — are exactly suited to debounce timeouts.)
  • Timestamp lockout. On each accepted event, record the time and ignore further events for the window duration. Simplest of all — but it accepts the first edge rather than the settled state, so it can register a press on a noise glitch; fine for non-critical UI, wrong for anything that matters.

A button handler is also a natural small state machine — idle, maybe-pressed, pressed, maybe-released — and writing it as one makes extensions like long-press detection, auto-repeat, and double-click fall out naturally instead of accumulating as flags.

Choosing Between Hardware and Firmware

SituationRecommended approach
Ordinary UI button, firmware runningFirmware (periodic sampling)
Button must wake MCU from deep sleepHardware (RC + hysteresis) — then confirm in firmware after wake
Signal drives a hardware counter/timer/clock inputHardware — firmware never sees the edges
Many buttons (matrix keypad)Firmware — the scan loop debounces naturally if each key is sampled slower than the bounce time, with per-key state
Switch on a long cable or external panelHardware for EMI/ESD reasons anyway (filter + protection), plus firmware window
Relay/limit-switch input to safety-relevant logicHardware conditioning plus a deliberately conservative firmware window

Design Considerations

  • Measure the real switch before choosing the window. Bounce time varies between switch models and grows with mechanical wear. A window sized to a scope measurement of the production switch (plus margin) beats any rule of thumb — and if the switch datasheet specifies bounce time, design to that figure with margin.
  • Debounce press and release separately if behaviour differs. Some switches bounce far longer on release; a single symmetric window sized only to the press measurement can still glitch on release.
  • Don't put logic in the edge ISR. A bouncing contact can deliver dozens of interrupts in a few milliseconds; an ISR that does real work per edge multiplies that cost and can starve other interrupts. First edge starts the process; the timer or sampler decides.
  • Think about what happens during the window. A 30 ms debounce window adds 30 ms of input latency. For most UI this is imperceptible; for a jog-wheel, game input, or machine stop button, latency budgets may push you toward accepting the first edge with hardware conditioning instead.
  • Keep ESD and EMI in the same conversation. Panel-mounted switches on cables pick up ESD strikes and coupled noise that look exactly like bounce to firmware. The RC filter, TVS protection, and debounce strategy are one combined input-conditioning design, not three separate afterthoughts.

Input handling is one of those details that separates a demo from a product — Zeus Design's firmware team builds production-grade embedded firmware where the buttons, encoders, and limit switches work every time.

Common Mistakes

  • Counting edge interrupts as events. The most common debounce bug: an interrupt counter assumes one interrupt per press, and a bouncing contact delivers five. Any design that counts edges needs the edges cleaned — in hardware or by the interrupt-plus-timer pattern — first.
  • Adding a capacitor without hysteresis. A capacitor alone converts fast bounce into a slow ramp, which an input without adequate hysteresis can read as more transitions, not fewer. Check the MCU datasheet's input hysteresis specification, or add a Schmitt buffer.
  • Copying a "50 ms delay after first edge" blocking pattern. delay(50) inside the handler works in a blinky demo and freezes everything else in a real product. Debounce state belongs in a non-blocking sampler or timer, not a busy-wait.
  • Debouncing in firmware on a wake pin. If the MCU is in deep sleep, firmware isn't running when the bounce arrives — each bounce edge can trigger a wake (or worse, confuse the wake logic). Wake inputs need hardware conditioning.
  • Forgetting the capacitor discharge path through the switch. A large capacitor directly across switch contacts discharges through them at every press with no current limit — degrading contacts rated for logic-level currents. Put a series resistor in the discharge path when using an RC debounce network.

Frequently Asked Questions

How long should the debounce window be?
Long enough to outlast the switch's worst-case bounce, short enough not to feel laggy. Published empirical measurements (notably Jack Ganssle's tests across a range of switches) found most switches settle within a few milliseconds, with outliers beyond 10 ms — so 10–20 ms covers typical tactile switches with margin, and 20–50 ms is a conservative choice that still feels instantaneous to a human (perceived lag generally becomes noticeable somewhere beyond 50–100 ms). If the product uses a specific switch part, the datasheet's bounce-time specification (where given) plus margin beats any generic number — and a five-minute oscilloscope session on the real switch beats the datasheet.
Should I debounce with an interrupt or by polling?
For ordinary user buttons, periodic polling (sampling the pin every 1–10 ms from a timer tick or RTOS task and requiring N consecutive identical samples) is simpler, robust, and uses negligible CPU. Interrupts earn their complexity when the input must wake the MCU from sleep or when polling latency is genuinely unacceptable. The robust interrupt pattern is: on the first edge, disable (or ignore) further edge interrupts for that pin and start a one-shot timer for the debounce window; when the timer fires, read the settled pin level and re-enable the interrupt. Processing bounce inside the ISR itself — or incrementing a counter on every edge interrupt — is the classic mistake, since a bouncing contact can generate dozens of interrupts per press.
Do rotary encoders need debouncing too?
Mechanical (contact-based) quadrature encoders bounce just like switches, but the fix is different: because the two quadrature signals encode direction in their sequence, the standard approach is a state-machine decoder that samples both channels and only counts valid quadrature transitions — invalid (bounce-induced) sequences are inherently rejected. Naively counting edges on one channel, or using edge interrupts without sequence validation, produces the classic symptom of an encoder that skips or jumps backwards. Optical and magnetic encoders don't bounce, though their outputs can still carry noise on long cables. See [how do you interface a rotary encoder to a microcontroller?](/questions/rotary-encoder-quadrature-interfacing) for the full quadrature decoding state machine and the hardware-decoder alternative.
Why does my external interrupt fire multiple times per button press even with a capacitor fitted?
A capacitor alone slows the edge but doesn't square it up — the pin voltage now crawls slowly through the input threshold region, and an ordinary CMOS input (without hysteresis) can interpret noise riding on that slow ramp as multiple crossings. Many MCU GPIO inputs do have some Schmidt-style hysteresis (check the datasheet's VIH/VIL hysteresis spec), but it's often small. The fixes: use an RC time constant genuinely longer than the bounce, feed the RC through a proper Schmitt-trigger buffer, or stop relying on the analogue filter and apply a firmware debounce window after the first edge.

References

Related Questions

Related Forum Discussions