That is a great reference! For the more EE-squimish, here is something more simplified, based on what I teach my class on a first pass introduction to the subject:
Take away the control system, all the fancy stuff and think about a single leaf switch mounted to a pinball machine and an Arduino connected to the switch. I'm using the Arduino for simplicity's sake. It runs a single program, running a single loop, that is constantly checking to see if that switch is open or closed. What we care about in pinball is when a switch transitions from open to closed and back to open again.
We assume that switches attached to targets close because the ball strikes them, but that's not always the case. Pinball machines get bumped and jostled and even adjacent coils firing can momentarily close those switches just from vibrations. Since those vibrations can close switches when the ball isn't there, we don't want is to treat EVERY closure as a result of the ball hitting it... We need to throw away these spurious closures and, in essence, this is what debouncing is.
One approach to solve this problem, is to scan the switch less frequently. Suppose I slow this down so I only read the switch every 10ms. If I see a closure twice in a row, then I know that the switch was closed for 10ms, so something_ is clearly pressing that switch down. Consider the following, where I check the switch every 10ms (at exactly times 0, 10, 20, 30 below) where '+' is closed and '0' is open
time = 0------10-----20-----30----40
state = ++++++++00000000000000
Using the this tactic of ensuring that two consecutive reads report closed (the "debounce" part is to throwing away a closure that doesn't read as closed twice in a row), this works in the example above, because the switch was closed at the read that occurred at time 0, and again at time 10 (from my high quality ASCII art it appears that the switch was closed for about 14ms).
10ms is a very long time to wait between scans, and as a result we could wind up missing perfectly good closures, even those that are 10ms long! Consider the following:
time = 0------10-----20-----30----40
state = 00+++++++0000000000000
The closure starts at time 4ms, but we didn't see it at time zero. We see it for the first time at time 10, but it's done by 18ms (before 20ms) so we don't see it on the next scan!
So, even if I want to say it needs to be closed for 10ms, those scans are too infrequent/slow. We missed a 10ms closure! If we scanned every 5ms, we would have seen it at time 5ms and again at 10ms, and again at time 15ms, so our rule would have to be that we want to see the switch as closed three times. Even 5ms will break down if we come up with some worst-case examples. So it would be better for me to physically scan every 2ms (or faster for that matter) and use software based logic to determine how many consecutive closures would be from a ball-strike instead of a random vibration.
It's slightly more complicated than this simplistic explanation, but not very. Vibrations can also open a legitimate closure early, which is covered in the excellent article wolfmarsh posted.
If we look at the history, debounce values have evolved based on hardware capabilities, increased ball speed, and another of other factors. It needs to be data-driven, and carefully determined. If the hardware scans as quickly as possible, you can refine what you do with consecutive values the values in software. If you are scanning too slowly in the hardware, you might not see enough of the truth to make a good guess.
I don't explain any of this when I teach my P-ROC/PyProcGame unit. I do it in the unit before, when we wire up an Arduino to a pinball switch matrix --on the P-ROC is just works