The Arduino tutorial says: wire a button between a pin and ground, then read the pin. That works… sometimes. Other times, the pin reads HIGH when nothing's pressed. Hover your hand near the wire and it changes. Walk past a fluorescent light and it changes. What's going on?
An input pin is an antenna
A digital input pin on the ATmega328P presents an extremely high impedance to the outside world — on the order of 100 MΩ. That's good: it means the chip draws essentially no current from whatever's connected to it. But high impedance has a side effect.
When the switch is open, the pin isn't connected to anything in particular. It's not at 5 V (no path to the rail), not at ground (no path to ground), just… floating. A floating pin behaves like a tiny antenna. The 50 Hz or 60 Hz mains hum in the room couples capacitively into the wire. The wire next to it carries some signal that crosstalks. Your hand carries enough static charge to push the pin one way or the other.
So you read the pin and get garbage. Sometimes HIGH, sometimes LOW. The chip isn't broken; the circuit is incomplete.
The fix is one resistor
Connect a 10 kΩ resistor between the pin and the 5 V rail. Now, when the switch is open, the pin is connected through the resistor to 5 V. The current is microscopic (5 V / 10 kΩ = 0.5 mA), but it's enough to firmly hold the pin at HIGH. When you press the button, the switch closes the path to ground, which has effectively zero resistance — much less than the resistor — so the pin snaps to LOW.
That's a pull-up. The variant where the resistor goes to ground (and the button to 5 V) is a pull-down. Same idea, opposite polarity. The resistor's job is to define the resting state so the pin always reads something deliberate.
The chip ships with pull-ups built in
The ATmega328P has internal pull-ups — typically 20–50 kΩ — on every digital pin. You enable them by writing pinMode(buttonPin, INPUT_PULLUP) instead of INPUT. Now the chip itself holds the pin HIGH at rest. No external resistor needed. The button just has to short the pin to ground when pressed.
The cost is that pressed = LOW, released = HIGH — the polarity feels backwards compared to the naive expectation. You write if (!digitalRead(buttonPin)) or define const bool PRESSED = LOW;. Worth it: you eliminate a part from your circuit and a confused beginner from your future.
Behind that one missing resistor lurks a whole world: impedance, capacitive coupling, the difference between "no connection" and "connected to a defined potential." Every reliable digital input you ever wire up — for a sensor, a limit switch, a hall-effect tachometer — is making the same bet you made with the button. Define your rest state, or the world will pick one for you.