ArduinoExplained

Arduino INPUT_PULLUP: What It Actually Does

One word in pinMode() replaces a resistor, a breadboard row and two jumper wires — and inverts your button logic in a way that catches everyone the first time. Here's what's actually happening inside the chip.

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

That's the whole circuit. No resistor, no connection to 5V.

The bit that catches everyone: the logic is backwards

With 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:

When to use a real resistor instead

SituationUse
Button on the same breadboard*INPUT_PULLUP
Long wire run, or an electrically noisy environmentExternal 10kΩ
Sensor whose datasheet specifies a value (DS18B20: 4.7kΩ)Fit exactly what it specifies
I2C busUsually already on the module — see the I2C guide
You need a pull-downExternal 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