The problem: a floating input
A digital input pin asks one question: is the voltage here high, or low?
Now picture a button wired between a pin and ground. Press it, and the pin is firmly connected to ground — that's a clear low. Release it, and the pin is connected to… nothing at all.
An unconnected input pin is called floating, and floating does not mean zero. The pin is extremely sensitive, and with nothing driving it, it picks up stray electrical noise from the air, nearby wires, even your hand moving near the board. It'll read high, then low, then high again, essentially at random.
This is why a button project that "works but randomly triggers on its own" is almost never a code problem.
The fix
Connect the pin, through a resistor, to a known voltage. Now when the button isn't pressed, the pin has somewhere definite to sit.
Pull-up resistor
Resistor between the pin and the positive supply (3.3V or 5V). The pin sits high when idle. Press a button wired to ground, and the pin is dragged low.
Note that this inverts what you might expect in code: not pressed = HIGH, pressed = LOW. Perfectly normal, and it catches people out constantly.
Pull-down resistor
Resistor between the pin and ground. The pin sits low when idle. Press a button wired to the positive supply, and the pin goes high.
This matches intuition better — pressed = HIGH — but pull-ups are more common in practice, partly because most microcontrollers include them internally.
Why the resistor at all? Why not wire straight to 3.3V?
Good question, and the answer is the whole point of the component.
Without the resistor, pressing the button would connect your positive supply directly to ground — a dead short. Something would get hot and something would break.
The resistor is what lets both things be true at once: enough current flows to hold the pin at a definite voltage, but when the button closes, the current through that path is limited to something harmless. It's a compromise component, and it's doing more work than it looks like.
What value?
10kΩ is the standard answer for a button, and it's right the overwhelming majority of the time.
The reasoning behind it:
- Too large (say 1MΩ) and the pull is weak — noise can still overcome it, and you're back to unreliable readings.
- Too small (say 100Ω) and it works, but wastes current continuously whenever the button is held. On battery projects that matters.
10k sits comfortably in the middle. Faster signals sometimes want lower values — I2C buses commonly use something in the 1k–4.7k range — because a stronger pull-up lets the line rise faster.
The good news: your board probably has them built in
Both Arduino and Raspberry Pi have internal pull-up resistors you can switch on in software. For a simple button, that means no external resistor at all.
Arduino:
pinMode(2, INPUT_PULLUP); // internal pull-up on
// button wired between pin 2 and GND
// reads HIGH when released, LOW when pressed
Raspberry Pi (using gpiozero, which handles the pull-up for you):
from gpiozero import Button
button = Button(17) # pull-up enabled by default
button.wait_for_press()
print("pressed")
Wire the button between the pin and ground, enable the internal pull-up, and you're done. This is why so many tutorials get away without mentioning resistors at all — and also why the ones that do mention them feel confusing.
When you still need a physical resistor
- Internal pull-ups are relatively weak — typically tens of kilohms. With long wires or an electrically noisy environment, an external 10kΩ gives a firmer hold.
- Pull-downs aren't always available internally. Some chips only offer internal pull-ups. The Raspberry Pi supports both; several Arduino boards only do pull-ups.
- Some sensors specify one. A DS18B20 temperature sensor needs an external 4.7kΩ pull-up on its data line, and a DHT22 typically wants around 10kΩ. These are documented requirements, not optional extras — the sensor won't work reliably without one.
- I2C on long runs or with many devices. Usually handled by the board and modules, but occasionally needs attention (see the I2C troubleshooting guide).
One more thing: bouncing
Even with a perfect pull-up, a mechanical button doesn't switch cleanly. The metal contacts physically bounce on contact, and the pin sees several rapid transitions in the few milliseconds after a press. Your code, being much faster than your finger, reads all of them — so one press registers as three.
That's a separate problem from floating, and it's solved separately: either a short delay in software after the first change, or a small capacitor across the switch to smooth the transitions. Libraries like gpiozero handle it for you by default.
The summary
- A disconnected input pin reads random noise. That's floating.
- A pull-up or pull-down resistor gives it a definite resting state.
- 10kΩ is the default value for a button. I2C uses lower.
- For most simple buttons, use your board's internal pull-up and skip the component entirely.
- If a sensor's documentation specifies a pull-up value, fit it — that's a requirement, not a suggestion.