Source: https://mokxi.com/learn/arduino-button-counter
Updated: 2026-10-25

Learn

# An Arduino button counter that counts each press once

Intermediate

Written by the Mokxi team, updated October 25, 2026

Start building free Open the editor

Or see every lesson.

- 455 parts on the bench
- 43 boards running now
- 1.00x real time, on every board

Press Run, then press the home or guest button

Press home or guest for a point; hold one for a second to take a point back.

To count button presses, count the moment the button goes down, not every time loop() finds it down. Keep what the button was last time around; when it is down now and was up before, that is one press. Then wait out the few milliseconds of contact bounce before believing a change, or one press counts as several.

The circuit above is a scoreboard on an Uno: a MAX7219 eight-digit display, with the home score on the left, a game clock in the middle and the guest score on the right. Press Run, then the home or guest button, and that score goes up by one. Hold a button for a second and the score goes back down one, for a point given by mistake. The third button starts and stops the clock.

Below is the counter built up from the mistake everyone makes first, with the counts each version gave for the same two presses on an Uno, a long press that clears the count, and two buttons that count up and down.

## Counting while it is held

The first try adds one whenever the pin reads LOW. loop() runs tens of thousands of times a second, so one press counted 22977 in the first 200 ms it was held, and the next reading was -30752: an int cannot go past 32767, so it wrapped around to the negative numbers. The button is wired from pin 2 to GND, with INPUT_PULLUP, so pressed is LOW.

Counts every pass, not every press

## State change detection

Keep the reading from last time and count only the change from up to down. That is state change detection, or edge detection. Two presses now counted 10, not tens of thousands, but still not 2.

The other 8 are bounce. A button’s metal contacts spring apart and touch again several times in the first few milliseconds of a press and of a release, and the sketch is fast enough to see each touch as a new press. Mokxi’s pushbutton bounces like a real one, which is why it shows up here.

Counts each change, bounces too

## Debounced: one press, one count

Only believe the button once it has read the same for a while. The sketch below notes when the reading last changed and accepts the new state only after 20 ms without a change. The same two presses printed "presses: 1" and "presses: 2". No delay() is needed, so the rest of loop() keeps running.

A press counter with a 20 ms debounce

## A long press that clears the count

To tell a press from a hold, note when the button went down and count on the way up instead. If it was held for a second, do the hold’s job and skip the count. With two short presses and then a long one, a version of the sketch above built this way printed "presses: 1", "presses: 2" and "held: back to 0".

The scoreboard running above does exactly this in its check() function: a release after at least 20 ms is a press, and a hold of 1000 ms is a hold, which takes a point back.

firmware/uno/examples/scoreboard (press or hold, from the sketch running above)

## Why not count with an interrupt?

attachInterrupt() on pin 2 runs a function the instant the pin changes, which sounds made for counting. For a button it is the wrong tool: the interrupt sees every bounce too, so it counts too many just as the state change sketch did, and debouncing inside an interrupt handler means timing with millis() there, which is fiddly. An interrupt earns its place for pulses too fast or too short for loop() to catch, such as a flow sensor or a wheel encoder, and the interrupts page has a counter for those.

A press lasts tens of milliseconds at the shortest and loop() comes around thousands of times in that, so reading the pin in loop() never misses one, as long as nothing in loop() blocks for long. A delay(1000) somewhere else in the sketch is what makes a counter miss presses.

## Keep the count after a reset

A variable starts again from 0 every time the board resets or loses power. To keep a count, save it with EEPROM.put(0, count) when it changes and read it back with EEPROM.get(0, count) in setup(). The EEPROM wears out after about 100,000 writes to one address, so for a counter that changes often, save it every so many presses or when a button says so, not on every press.

## Up and down with two buttons

Two buttons, two remembered states. The sketch below counts up on pin 2 and down on pin 3 and keeps the count between 0 and 99 with constrain(). One press of down, two of up and one of down printed 0, 1, 2 and 1: the first press could not go below 0. A short delay(20) at the end of loop() is the simplest debounce when the sketch has nothing else to do.

From here, show the count on a display: the seven-segment, TM1637 and LCD pages each print a number. On an Uno, an int counts to 32767; use an unsigned long for more.

Count up and down

## Questions

How do I count button presses on an Arduino?

Remember the button’s state from the last pass through loop() and add one only when it changes from up to down. Debounce it, or one press counts several times.

Why does my button counter count more than once per press?

Contact bounce: the button makes and breaks contact several times in the first few milliseconds. Accept a change only after the reading has held for about 20 ms.

Why does my Arduino counter go negative?

It is counting every pass of loop() while the button is held, and an int wraps from 32767 to -32768. Count the change from up to down instead.

How do I detect a long press on an Arduino button?

Save millis() when the button goes down. If it is still down a second later, it is a long press; if it comes up sooner, it was a short one.

Related

## Keep going

Arduino Button Debounce: See the Bounce, Then Fix It Arduino digitalRead(): Buttons, HIGH and LOW Debouncing a Button: Why It Fires More Than Once Arduino State Machine: A Four-State Reaction Game Arduino millis(): Timers and Multiple Tasks Arduino TM1637 Display: Library and Countdown Arduino Interrupts: attachInterrupt on Pin 2 and 3 The scoreboard project Intermediate The push button part The project page, with the full sketch Intermediate

Sources

## Where the facts on this page come from

- Arduino: state change detection
- Arduino: debounce

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building free Open the editor
