Resistor between a push button and a microcontroller pin on dark background

Pull-Up and Pull-Down Resistors: Why Floating Pins Lie

A pull-up or pull-down resistor gives a digital input pin a definite voltage to rest at when nothing else is driving it, so the pin always reads a clean HIGH or LOW instead of random noise. A pull-up connects the pin to the supply rail through a resistor (typically 10kΩ), so the pin idles HIGH until something like a button pulls it LOW. A pull-down does the opposite: it ties the pin to ground so it idles LOW. Without one of these, an unconnected input is said to be floating, and a floating pin will happily read HIGH one moment and LOW the next, depending on nearby electrical noise. That is why a button circuit with no resistor seems haunted. The fix costs one resistor, or on most microcontrollers, one line of code.

What a floating pin actually is

A digital input pin on a microcontroller has extremely high input impedance. Impedance is just resistance to alternating signals; high impedance means the pin draws almost no current and takes almost nothing to influence. That is great for reading sensors, but it also means the pin is a tiny antenna. Your hand near the board, the 230V mains wiring in the wall, a mobile phone on the desk: all of it couples small voltages into the pin.

The chip has to classify whatever voltage it sees as either HIGH or LOW. There is no third option called "nothing connected". So a floating pin does not read as some safe neutral value. It reads as whatever the noise happens to be at that instant, and it flips constantly. If your code does something on a button press, a floating pin can fire that action hundreds of times per second with nobody touching anything.

How a pull-up resistor fixes it

Connect a resistor from the input pin to the positive supply (5V on an Arduino Uno R3, 3.3V on an ESP32). Now, when nothing else is connected, a tiny current flows through the resistor into the pin's high impedance, and the pin sits solidly at the supply voltage. It reads HIGH, every time.

Then wire your push button between the pin and ground. When you press it, the button makes a direct, low-resistance path to ground. Ground wins the tug-of-war easily because the pull-up resistor limits how hard the supply can pull. The pin drops to nearly 0V and reads LOW. Release the button and the pull-up takes over again.

Notice the logic is inverted: pressed reads LOW, released reads HIGH. This trips up many beginners, but it is the standard arrangement because it is simple and robust.

Pull-down: the mirror image

A pull-down resistor goes from the pin to ground, so the pin idles LOW, and the button connects the pin to the supply so pressing reads HIGH. The logic feels more natural, but pull-ups are far more common in practice, mainly because almost every microcontroller has pull-up resistors built into the chip. On the Uno you enable one with pinMode(2, INPUT_PULLUP); and you need no external resistor at all. Built-in pull-downs are rarer (the ESP32 has them, classic AVR chips do not).

Choosing the value

10kΩ is the everyday default, and any resistor variety pack will have plenty. The trade-off works like this: a smaller value (say 1kΩ, called a "strong" pull-up) fights noise better and suits long wires, but wastes more current whenever the button is held down (5V across 1kΩ is 5mA). A larger value (100kΩ, a "weak" pull-up) saves battery power but is more easily disturbed by noise and by the pin's own leakage. Between 4.7kΩ and 47kΩ, almost anything works for a button on a short wire. Internal pull-ups on the Uno are roughly 20kΩ to 50kΩ.

Worked example: one button on an Arduino Uno R3

Push a tactile button into a 400-point breadboard so it straddles the centre channel. Run a Dupont jumper wire from one side of the button to digital pin 2, and another from the other side to GND. That is the whole circuit.

In code: pinMode(2, INPUT_PULLUP); in setup, then digitalRead(2) in the loop. It returns HIGH while the button is up and LOW while pressed. If you want an external version instead, put a 10kΩ resistor from pin 2 to 5V and keep the button from pin 2 to GND; electrically it behaves the same way.

Where this bites you

The classic mistake is wiring a button between the pin and 5V, with nothing else, and testing it. Pressed, it reads HIGH, perfect. Released, the pin is connected to nothing, so it floats. The project works on the bench, then misbehaves the moment you move it near a motor, a relay, or a wall socket, because the released state was never defined. The bug feels intermittent and unrelated to the button, which makes it maddening to trace. The rule of thumb: every digital input must always have a defined path to a voltage, in every switch position. If you can point at a moment where the pin connects to nothing, that is a floating input.

A second trap: enabling INPUT_PULLUP but still wiring the button to 5V. Now pressing the button connects 5V to a pin that expects to be pulled LOW, the reading never changes, and if you had accidentally set the pin as OUTPUT LOW you would be shorting 5V into the pin. Button to ground, always, when using pull-ups.

FAQ

Do I need a pull-up on every unused pin?

No. Unused pins that your code never reads can float without causing logic errors, though they do burn a little extra power as their input stages switch with noise. For low-power battery projects, it is good practice to enable internal pull-ups on unused inputs or set them as outputs. For a hobby project on USB power, you can ignore them.

Why does my button work in one sketch but not another?

Check whether the working sketch used INPUT_PULLUP and the broken one used plain INPUT. With plain INPUT and no external resistor, the pin floats and the behaviour depends on the room, the wiring, even your hand. Same hardware, different pin mode, completely different reliability.

Can I use a pull-up and pull-down on the same pin?

Together they form a voltage divider that parks the pin somewhere in the middle, which is the one place you never want a digital input to sit. Pick one, based on which idle state you want.

When you are ready to put a button into a real project, Compoden's AI build assistant Soldr can wire it into your build and generate code that reads it correctly, pull-up included.

Back to blog