Your current logic is "If the button is down, change the LED state."
Instead, you should only change the LED state when the button changes state; either from high to low, or low to high. (The difference is one changes state when you press, the other when you release; but which one is which depends on how you wire your buttons.)
When the button is pressed or released, there is a very short duration when the connection is intermittent. Depending on the type of the button, the duration varies. If you are unlucky, you check the button state during such an intermittent connection, and get the "wrong" state. You will get the correct state on a later check, but if you check purely state changes --
edges -- you can see more changes than the actual button does, due to contact bounce.
Consider the following example code:
#define LED_PIN 2
#define BUTTON_PIN 3
#define LOOP_PIN 4
static int led_state;
static int button_state;
static int loop_state;
void setup(void) {
pinMode(LED_PIN, OUTPUT);
pinMode(LOOP_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
led_state = 0;
loop_state = 0;
button_pressed = 0;
}
void loop(void) {
if (digitalRead(BUTTON_PIN)) {
if (button_pressed) {
// Button is kept depressed
} else {
button_pressed = 1;
// Button press
led_state = !led_state;
digitalWrite(LED_PIN, led_state);
}
} else {
if (button_pressed) {
button_pressed = 0;
// Button was released
led_state = !led_state;
digitalWrite(LED_PIN, led_state);
} else {
// Button is not being pressed
}
}
loop_state = !loop_state;
digitalWrite(LOOP_PIN, loop_state);
delay(10); // 10ms = 0.01s, 1000/10 = 100 iterations per second.
}
This example changes the state of the LED on both press (transition from not-pressed to pressed) and release (transition from pressed to not-pressed).
Because of how it is written, I do believe it is immune to contact bounce -- it is, after all, just a four-state finite state machine.
The intent of the above example is to show how to properly check button state changes, but without (software) debouncing to keep it as simple as possible.
If you get flicker on the LED pin, that will be due to button contact bounce. (You'll need an oscilloscope to capture it, though.)
If the button was e.g. "Move up in menu", it'd cause the selection to move by more than one entry per button press. Or game character to do the action several times instead of just once. Not good.
That contact bounce can be suppressed using a capacitor, or in software in various ways. I personally prefer software debounce that reacts to the state change immediately, but is "numb" to any further changes for a short period (typically on the order of 20ms, or 0.020 seconds). Any contact bounce occurs during the "numb" period, and any extra transitions during that period are ignored. I get immediate response, with a quaranteed interval between press and release events (transitions -- the "numb" period), and it also makes implementing autorepeat (by keeping the button down for longer) easy. (It basically uses a state-and-counter variable per button.)