Pull-up and pull-down
A digital input has to be held at a voltage to read as a 0 or a 1. Left unconnected it is held at nothing, and it will report both, changing as your hand moves nearby. A pull-up or pull-down resistor is the part that decides what the pin reads when nothing else is driving it.
Three wiring choices, one pin, read twelve times a second. Hold the button in each mode.
What a floating pin actually does
Configure a pin as an input and it becomes a very high impedance node — it draws almost no current, which is the whole point of an input. The cost is that almost nothing is needed to move its voltage. The capacitance of the trace, the leakage across the breadboard, and the mains field coupling through your body are all enough. Read that pin in a loop and you will see it flicker between 0 and 1 in no pattern.
This is the single most common cause of a button that "works sometimes". The button is fine. When it is not pressed, the pin is connected to nothing at all.
The fix is one resistor
Add a resistor from the pin to 3V3 and the pin is now weakly held high. Press the button and it connects the pin straight to ground, which wins easily, so the pin goes low. Release it and the resistor pulls it back to 3.3 V. That is a pull-up, and it inverts your logic: pressed reads 0.
Both paths reach the same pin. The resistor is weak, the button is not, so the button decides whenever it is closed.
Picking the value
Only two things matter. A smaller resistor holds the line more firmly and switches faster, and it wastes more current whenever the line is pulled low. A larger one wastes almost nothing and is slower and easier to disturb. Between 4.7 kΩ and 10 kΩ is right for nearly everything at 3.3 V.
Pull-downs, and when to prefer them
Move the resistor to ground and everything inverts: the pin idles low, and the button connects it to 3V3 so pressed reads 1. That reads more naturally in code, and it is the right choice when a floating-high pin would be dangerous — an enable line on a motor driver, for example, should sit at 0 until you deliberately raise it.
Pull-ups are still more common, for two reasons. Most microcontrollers, including every ESP32, have internal pull-ups on more pins than they have internal pull-downs. And open-drain buses such as I2C are built on the assumption that the idle state is high.
Use the internal one first
There is a resistor inside the chip on most pins, roughly 45 kΩ on the ESP32, and one line of code connects it. For a button that is all you need — no part, no wire.
#define BUTTON 4
void setup() {
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP); // idles high, no external part
}
void loop() {
bool pressed = !digitalRead(BUTTON); // inverted: low means pressed
if (pressed) Serial.println("down");
delay(120); // crude debounce
}The internal resistor is also too weak for anything fast. At 45 kΩ an I2C bus will not meet its rise time, which is why every breakout module carries its own 4.7 kΩ or 10 kΩ pair on board.
Four ways this goes wrong
Two modules, two pull-up pairs. Four 4.7 kΩ resistors in parallel is 1.2 kΩ. Add a third module and the bus starts failing. Remove the on-board pairs from all but one.
Pulled up to 5 V. The resistor's other end must go to the same rail as the chip. Pulling a 3.3 V input up to 5 V puts 5 V on that pin whenever the line is idle.
A pull-up on an output. Harmless but pointless. An output drives the line itself. The exception is open-drain outputs, which can only pull low and need the resistor to get back high.
Fighting a strapping pin. Some pins are read at boot to decide how the chip starts. Holding one of those low with a pull-down can stop the board from booting at all. Check the chip page before you commit a pin.
I = 3.3 V / R while the line is low. Rise time is roughly 0.85 × R × C, which is why big resistors round off I2C edges.
Edit this page — content/fundamentals/electricity/pull-up-and-pull-down.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.