The Direct Answer: Which Switch and Wiring Topology Wins?

If you are wiring an Arduino with push button switches for a standard DIY project, stop overthinking the BOM (Bill of Materials). Use an Omron B3F-1000 12mm tactile switch wired to a digital input using the microcontroller's internal pull-up resistor (INPUT_PULLUP), combined with a 50-millisecond software debounce routine.

Here is the decision path to finalize your hardware design:

Project ScenarioSwitch TypeWiring TopologyVerdict & Part Pick
Standard UI / Menu navigation12mm Tactile (Omron B3F)Active-Low via Internal Pull-upDEFAULT PICK: Omron B3F-1000 + INPUT_PULLUP
High-EMI environment (motors nearby)Mechanical (Cherry MX)Active-Low + 10kΩ External Pull-up + 100nF CapCherry MX1A-11NW + Hardware RC Filter
Safety E-Stop / IndustrialPanel Mount MushroomActive-High + External Pull-down + Hardware InterlockSchneider XB4BS8442 (NC contacts, defer to licensed panel builder)
Maker Tip: Never use external pull-down resistors (wiring the switch to +5V) for standard buttons. Active-low (wiring to GND) is the industry standard because it protects the GPIO pin from accidental short circuits to ground and leverages the ATmega328P's built-in 20kΩ-50kΩ pull-up resistors.

Hardware Spec Sheet & Exact Parts List

This build targets the Arduino Nano v3 (ATmega328P, 16MHz, 5V logic). The Nano is preferred over the Uno for breadboard projects because it leaves two open power rails on a standard 830-point breadboard.

ComponentExact Part / ValueSpecs & Notes
MicrocontrollerArduino Nano v3 (Clone or Genuine)ATmega328P, 5V logic, 16MHz crystal. Ensure CH340 or FT232RL USB driver is installed.
Push ButtonOmron B3F-100012x12mm tactile, 160gf actuation force, 4-pin DIP.
Hardware Debounce Cap100nF (0.1µF) CeramicOptional. Placed in parallel with the switch for RC filtering.
Indicator LEDStandard 5mm Red LEDVf = 2.0V, If = 20mA max.
Current Limiting Resistor220Ω (1/4W)Yields ~13.6mA through the LED ((5V - 2V) / 220Ω).

Wiring the Arduino with Push Button Switches

The number one reason beginners fail when wiring a tactile switch is the 90-degree rotation trap. Standard 12mm tactile switches have four pins. Internally, pins 1 and 2 are permanently bridged, and pins 3 and 4 are permanently bridged. The switch only opens or closes the circuit between the (1,2) pair and the (3,4) pair when pressed.

If you rotate the switch 90 degrees on the breadboard, you will short the input directly to ground, and the button will appear 'stuck pressed'.

Pin Mapping Table

ComponentComponent PinArduino Nano PinWire Color (Recommended)
Push ButtonPin 1 or 2 (Either side)D2 (Digital Input)Yellow
Push ButtonPin 3 or 4 (Opposite side)GNDBlack
LEDAnode (Long leg)D13 (via 220Ω Resistor)Red
LEDCathode (Short leg)GNDBlack

Step-by-Step Wiring Procedure

  1. Seat the Nano: Straddle the Arduino Nano across the center trench of the breadboard. Ensure the USB port faces the edge.
  2. Seat the Switch: Push the Omron B3F-1000 into the board. Verification: The switch should sit flat. If it rocks, the pins are bent or you are forcing it across the trench incorrectly.
  3. Wire the Input: Run a jumper from one side of the switch to D2. Run a jumper from the opposite side of the switch to GND.
  4. Wire the Output: Insert the 220Ω resistor into D13, bridging to an empty row. Insert the LED anode into that row, and the cathode to the GND rail.
  5. Add the Filter (Optional): If your environment has heavy EMI (like a 3D printer stepper motor nearby), wedge the 100nF ceramic capacitor directly across the two switch pins used.

Bulletproof Debounce Code (Arduino Nano v3)

Mechanical contacts do not close cleanly. When the metal dome inside the B3F switch strikes the contact pad, it physically bounces, creating micro-arcs and rapid make/break connections for 5 to 50 milliseconds. If you read the pin directly in a loop(), a single press will register as 10 to 30 distinct presses.

The code below uses a non-blocking millis() state-machine to reject bounce. It targets the Arduino Nano v3 and includes serial debugging to help you visualize the bounce rejection in real-time.

// Target Board: Arduino Nano v3 (ATmega328P, 5V)
// Wiring: Button between D2 and GND. LED between D13 and GND (with 220R).

const uint8_t BTN_PIN = 2;
const uint8_t LED_PIN = 13;

// Debounce configuration
const unsigned long DEBOUNCE_DELAY_MS = 50; 

// State variables
bool ledState = false;
bool lastStableBtnState = HIGH; // HIGH because of INPUT_PULLUP
bool currentReading = HIGH;
unsigned long lastDebounceTime = 0;

void setup() {
  // Initialize serial for debugging at 115200 baud
  Serial.begin(115200);
  while (!Serial) { ; } // Wait for serial port (Native USB boards)
  
  // Configure pins
  pinMode(BTN_PIN, INPUT_PULLUP); // Enables internal 20k-50k pull-up resistor
  pinMode(LED_PIN, OUTPUT);
  
  digitalWrite(LED_PIN, ledState);
  Serial.println("SYS: Boot complete. Awaiting button press...");
}

void loop() {
  // Read the raw physical state of the pin
  bool rawReading = digitalRead(BTN_PIN);

  // If the raw reading differs from the last stable state, reset the timer
  if (rawReading != currentReading) {
    lastDebounceTime = millis();
    currentReading = rawReading;
  }

  // Check if the debounce delay has passed
  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY_MS) {
    
    // If the state has actually stabilized and changed
    if (currentReading != lastStableBtnState) {
      lastStableBtnState = currentReading;

      // We only trigger on the FALLING edge (button pressed, pin goes LOW)
      if (lastStableBtnState == LOW) {
        ledState = !ledState; // Toggle LED
        digitalWrite(LED_PIN, ledState);
        Serial.println("ACT: Button PRESSED (Stable LOW). LED Toggled.");
      } else {
        Serial.println("ACT: Button RELEASED (Stable HIGH).");
      }
    }
  } else {
    // Optional: Debug output to prove bounce is being rejected
    // Uncomment the line below to see bounce rejection in Serial Monitor
    // Serial.println("DBG: BOUNCE_REJECTED (dt < 50ms)");
  }
}

Debugging: First 3 Checks and Exact Error Strings

When your Arduino with push button circuit misbehaves, don't start rewriting code. 90% of failures are physical wiring faults. Open your Serial Monitor at 115200 baud and observe the behavior.

Symptom: Serial Monitor spams ACT: Button PRESSED randomly without touching the switch.

Ranked Causes & Fixes:

  1. Floating Input (Missing Pull-up): If you used pinMode(BTN_PIN, INPUT) instead of INPUT_PULLUP, the D2 pin is acting as an antenna, picking up 60Hz mains hum from your body. Fix: Change code to INPUT_PULLUP or add a 10kΩ physical resistor from D2 to 5V.
  2. Shared Ground Loop / Breadboard Noise: If you are powering a servo or motor from the same breadboard ground rail, voltage spikes are pulling the GND reference high, tricking the Nano into reading a LOW on D2. Fix: Move the button's ground wire to a dedicated GND pin on the Nano, separate from motor grounds.
  3. Failing Tactile Switch: Cheap clone switches suffer from internal oxidation, causing resistance to fluctuate between 10Ω and 2kΩ, which can confuse the internal pull-up voltage divider. Fix: Replace the switch with a genuine Omron or C&K component.

Symptom: LED toggles twice or three times per single physical press.

Ranked Causes & Fixes:

  1. Insufficient Debounce Delay: Heavy-duty mechanical switches (like Cherry MX Greens) can bounce for up to 20ms. If your DEBOUNCE_DELAY_MS is set to 10, you will catch the secondary bounce. Fix: Increase DEBOUNCE_DELAY_MS to 50. (See Jack Ganssle's definitive guide to debouncing for oscilloscope captures of switch bounce).
  2. Edge Detection Logic Flaw: You are triggering the LED toggle on the state rather than the edge. Fix: Ensure your code checks if (lastStableBtnState == LOW) only when it transitions, exactly as written in the code block above.

Symptom: Button appears 'Stuck Pressed' (LED toggles on boot, then ignores presses).

This is the 90-degree rotation trap mentioned earlier. You have wired Pin 1 and Pin 2 (which are internally bridged) to D2 and GND. You have created a dead short to ground. The ATmega328P's internal pull-up resistor is limiting the current, so you won't fry the chip, but the pin will permanently read LOW. Fix: Rotate the switch 90 degrees or move the wires to opposite sides of the switch body.

Extending and Simplifying the Build

Once you have a single button polling reliably, you will inevitably need to scale. Here is how to adapt the architecture based on your end goal.

How to Extend: Matrix Keypads and Interrupts

  • Scaling to 16+ Buttons: Do not wire 16 buttons to 16 GPIO pins. Use a 4x4 matrix keypad. This requires 8 pins total (4 rows, 4 columns) and the Keypad.h library. The matrix scans rows by pulling them LOW one at a time and reading the columns.
  • Ultra-Low Latency (Gaming/MIDI): Polling in the loop() introduces up to a few milliseconds of jitter depending on what else your code is doing. For MIDI controllers, move the button to D2 (which supports hardware interrupts on the Nano) and use attachInterrupt(digitalPinToInterrupt(BTN_PIN), isrFunction, FALLING). Note: You must debounce inside the ISR using a static millis() variable, or use a hardware RC filter to prevent the interrupt from firing 20 times per press.

How to Simplify: Pre-Wired Modules

If you are building a quick prototype and don't want to deal with bare tactile switches and breadboard jumpers, buy a pre-wired module. The KY-004 Key Switch Module includes the tactile switch, the pull-up resistor, and a status LED on a single PCB with 3 header pins (GND, VCC, Signal). Simply wire Signal to D2, GND to GND, and VCC to 5V. The code provided above will work with it flawlessly without any modifications.

Final Recommendation: For 95% of embedded projects, stick to bare Omron B3F switches with INPUT_PULLUP and software debouncing. It saves board space, reduces BOM costs, and teaches you the fundamental timing mechanics of microcontroller I/O that you will need when debugging more complex sensors.