Pull-Up vs Pull-Down Resistors Explained

Button registering phantom presses? Input flipping when you wave your hand nearby? Learn pull-up vs pull-down resistors: why CMOS inputs float, how to choose values (10k default, when to go stronger or weaker), internal pull-ups, open-drain outputs, and debouncing.

Written by Projectech7 min readPublished
For B.E./B.Tech Electronics and E&TC students wiring buttons, sensors, and digital inputs to microcontrollers in academic and final-year projects Topics: Digital Electronics, Microcontrollers, Resistors
Illustration comparing pull-up and pull-down resistor circuits with a pushbutton, showing idle and pressed voltage levels.
Illustration generated for this guide.
In this guide

You wire a pushbutton to a digital input, press it, and the microcontroller registers three presses. Or the input reads HIGH with nothing connected, then flips randomly when you wave your hand near the board. The button is fine. The code is fine. The pin is floating — and a floating CMOS input is an antenna that amplifies room noise into logic levels.

Pull-up and pull-down resistors are the fix: one resistor that defines the pin's voltage when nothing else is driving it. This guide explains why floating inputs misbehave, how pull-ups and pull-downs work, how to choose the resistor value, internal vs external resistors, their role in I2C, and the mistakes that waste power or invite noise.

Why inputs float (and why it's bad)

A microcontroller digital input is a CMOS gate with enormous input impedance — tens to hundreds of megaohms. It draws essentially no current, which means it also holds no definite voltage: with nothing connected, the pin's voltage drifts wherever leakage currents and nearby electric fields push it. Your hand approaching the board couples enough charge to flip it. That's not a faulty chip; it's physics.

Worse, a floating input can drift to mid-supply (~1.65V on a 3.3V chip), where both the NMOS and PMOS transistors in the input buffer conduct simultaneously — drawing excess supply current (shoot-through) and potentially oscillating. Unused MCU pins should never float: configure them as outputs driven low, or enable their internal pull resistors.

Pull-up vs pull-down

A pull-up connects the pin to VCC through a resistor; a pull-down connects it to ground through a resistor. The pin idles at the pulled level, and whatever drives the pin (a button to ground, a sensor's open-drain output) overpowers the resistor when active.

  Pull-up:                      Pull-down:
  VCC                           pin ---[10k]--- GND
   |                            |
  [10k]                        button
   |                            |
  pin --- button --- GND        VCC

With a pull-up and a button to ground: idle = HIGH, pressed = LOW (active-low logic). With a pull-down and a button to VCC: idle = LOW, pressed = HIGH (active-high). Both work; pull-up-to-ground-button is the more common convention (and matches most MCUs' internal pull-ups).

Pull-up Pull-down
Idle state HIGH LOW
Button connects to Ground VCC
Active level LOW (active-low) HIGH (active-high)
Current when active VCC/R flows through resistor VCC/R flows through resistor
Internal support Most MCUs have internal pull-ups Fewer MCUs have internal pull-downs

Choosing the resistor value

The value is a three-way tradeoff:

  1. Too large (e.g. 1MΩ): weak pull — noise and leakage can still move the pin; rise times get slow (bad for fast signals like I2C).
  2. Too small (e.g. 100Ω): strong pull, fast edges — but wastes power whenever the pin is driven against it: P = V²/R. A 100Ω pull-up on 5V burns 250mW with the button pressed.
  3. Just right: strong enough to beat noise, weak enough to sip power.

Standard choices:

Situation Typical value Reasoning
Pushbutton, short traces 10kΩ The universal default; 0.5mW at 5V when pressed
Battery-powered button 47kΩ–100kΩ Minimizes pressed-state current drain
Noisy environment / long wires 4.7kΩ Stiffer against induced noise
I2C bus 2.2kΩ–10kΩ Sized for rise time vs 3mA sink limit (see I2C guide)
Unused MCU pin Internal pull (20–50kΩ) Zero parts, adequate for a pin doing nothing

The 10kΩ default: on 5V it draws 0.5mA when the button is pressed — negligible for mains-powered projects, worth reconsidering (47kΩ+) if the button might be held for hours on battery power. For the ESP32's famously leaky deep-sleep, every external pull to ground is a sleep-current path — audit them in low-power designs (see the ESP32 low-power guide).

Internal pull-ups: use them

Nearly every MCU provides software-enableable internal pull-ups (typically 20–50kΩ):

// Arduino / AVR
pinMode(BUTTON_PIN, INPUT_PULLUP);  // enables the internal pull-up

// ESP32 (Arduino core)
pinMode(BUTTON_PIN, INPUT_PULLUP);

// STM32 (HAL)
// GPIO_InitStruct.Pull = GPIO_PULLUP;

Internal pull-ups are free, take no board space, and are perfectly adequate for buttons and idle-state definition on short traces. Their weaknesses: the exact value varies with process and temperature (don't rely on it for timing), they're too weak for I2C or long wires, and some pins (notably ESP32 strapping pins) behave specially at boot — check the datasheet before relying on internal pulls for boot-configuration pins.

Rule of thumb: internal pull-ups for buttons and unused pins; external resistors for buses, long wires, boot-strapping pins, and anything where you need a known value.

Pull-ups and open-drain outputs (I2C and beyond)

Some outputs — I2C's SDA/SCL, comparator outputs, many sensor interrupt pins — are open-drain: they can only pull LOW, never drive HIGH. The pull-up resistor is what creates the HIGH state. Without it, the line never goes high and the bus is dead.

This is why I2C requires pull-ups (covered in depth in the I2C debugging guide): both lines idle HIGH via the resistors, and devices pull them LOW to signal. A push-pull output must never be connected to an open-drain bus — two drivers fighting (one pushing HIGH, one pulling LOW) causes contention current and garbage.

Wired-AND bonus: multiple open-drain outputs can share one pull-up, and the line goes LOW if any device pulls it down — a free multi-device interrupt line with one resistor.

Debouncing: the resistor's partner problem

A pull-up defines the electrical idle state, but mechanical buttons bounce — the contacts chatter for 5–20ms, generating multiple edges per press. The resistor doesn't fix this; debouncing does:

// Simple software debounce
int reading = digitalRead(BUTTON_PIN);
if (reading != lastState) lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > 20) {
  if (reading != buttonState) {
    buttonState = reading;
    if (buttonState == LOW) { /* pressed (active-low) */ }
  }
}
lastState = reading;

Hardware alternative: an RC filter (10kΩ + 100nF ≈ 1ms time constant) before a Schmitt-trigger input. Software debouncing is usually simpler and free.

Common mistakes

  • No pull resistor at all — the floating-input lottery. The #1 beginner digital bug.
  • Pull-up on a pin that needs pull-down (or vice versa), then "fixing" it in software with inverted logic that confuses everyone later. Wire the idle state you actually want.
  • Forgetting internal pulls are weak — using INPUT_PULLUP for I2C or a 2-meter cable run, then blaming the sensor.
  • Pull resistors fighting during sleep — external pulls to VCC on pins that float in deep sleep create sneak current paths; audit every resistor in battery designs.
  • Too-strong pulls on battery buttons — 1kΩ pull-up with a button held down drains 5mA continuously. Size for the use case.
  • Active-low logic confusion — with the standard pull-up + button-to-ground, pressed = LOW. Write the code (and the comments) to match, or the next person will "fix" it.
  • Leaving ESP32 strapping pins pulled the wrong way at boot — some pins must be HIGH/LOW during reset to select boot mode. Check the strapping-pin table before adding resistors to GPIO0, GPIO2, GPIO12, GPIO15.

Quick checklist

  • Every digital input has a defined idle state (pull-up, pull-down, or driven output)
  • No MCU pin left floating — unused pins set as outputs or internally pulled
  • Resistor value chosen for the situation (10kΩ default; stronger for noise/buses, weaker for battery)
  • Buttons debounced in software or hardware
  • Active-low vs active-high logic documented in the code
  • Boot-strapping pins checked against the datasheet

Where to go from here

More project guides

More in Electronics / E&TC