The one-line version
pinMode(2, INPUT_PULLUP); // instead of pinMode(2, INPUT);
That switches on a resistor that already exists inside the microcontroller, connected between the pin and the supply rail. You get the pull-up without adding a component.
Why you need it at all
An input pin with nothing connected doesn't read zero — it floats, picking up electrical noise and reading high and low at random. A button only connects the pin to something while it's held down; the rest of the time the pin is dangling.
The pull-up gives the pin somewhere definite to sit when the button isn't pressed. The full explanation of floating inputs is here — this article is specifically about the Arduino shortcut.
Wire it like this
- One leg of the button → the pin (say pin 2)
- Other leg → GND
That's the whole circuit. No resistor, no connection to 5V.
The bit that catches everyone: the logic is backwards
INPUT_PULLUP, not pressed reads HIGH and pressed reads LOW. That's the opposite of what most people write first.
It follows from the wiring. Idle, the internal resistor ties the pin to 5V — HIGH. Press the button and you connect the pin straight to ground, which wins — LOW.
So the test you want is:
if (digitalRead(2) == LOW) {
// button IS pressed
}
Some people flip it in code to keep it readable:
bool pressed = !digitalRead(2);
if (pressed) { ... }
If your button appears permanently pressed, or does nothing until you release it, this inversion is almost always why — not a wiring fault.
How strong is the internal resistor?
On the ATmega328P — the chip in the Uno R3 — the internal pull-up is specified as a fairly wide range, commonly cited as roughly 30kΩ to 50kΩ. It is deliberately not a precision component.
That matters in two ways:
- It's weak. Compared with the 10kΩ you'd normally fit externally, 30–50kΩ pulls gently. Fine for a button an inch away; less reliable over a long cable or near anything electrically noisy.
- It isn't a guaranteed value. If your design depends on a specific resistance, fit a real resistor. The internal one is a convenience, not a specification.
When to use a real resistor instead
| Situation | Use |
|---|---|
| Button on the same breadboard | *INPUT_PULLUP |
| Long wire run, or an electrically noisy environment | External 10kΩ |
| Sensor whose datasheet specifies a value (DS18B20: 4.7kΩ) | Fit exactly what it specifies |
| I2C bus | Usually already on the module — see the I2C guide |
| You need a pull-down | External resistor — many Arduinos have no internal pull-down |
On the Raspberry Pi
Same idea, different syntax, and the Pi offers pull-downs internally too:
from gpiozero import Button
button = Button(17) # pull-up is the default
button = Button(17, pull_up=False) # pull-down instead
gpiozero also debounces for you, which saves a separate headache.
The other thing nobody warns you about: bouncing
A pull-up fixes floating. It does nothing about contact bounce — the metal contacts inside the button physically chatter for a few milliseconds on each press, and your code is fast enough to read every one of them. One press, three registered presses.
The usual fixes are a short delay after the first change, or checking that the state has stayed put for a few milliseconds before believing it. On the Pi, gpiozero handles it by default.
Summary
pinMode(pin, INPUT_PULLUP)— button between pin and ground, no resistor needed- Pressed = LOW, released = HIGH. It's inverted, and that's normal
- The internal resistor is roughly 30–50kΩ and not a guaranteed value
- Fit a real 10kΩ for long wires or noise; fit exactly what a sensor's datasheet asks for
- Bouncing is a separate problem needing a separate fix