a race condition
Imagine two roommates who each glance at the shared shopping list, both see that there is no milk, and both go out and buy milk. Now you have two cartons. Nothing was wrong with either person's reasoning; the trouble was that they acted on the same shared information at overlapping times, and the final result depended on exactly how their actions happened to interleave. A race condition in a program is the same kind of bug: two or more threads touch shared data at overlapping times, and the answer you get depends on the unpredictable order in which their steps happen to run.
Here is the classic case. A shared variable count holds 5, and two threads each run count = count + 1. You would expect the result to be 7. But count = count + 1 is not one indivisible action; the CPU does it in several steps: read count into a register (reads 5), add 1 (gets 6), write the register back to count (stores 6). If thread A reads 5, then thread B also reads 5 before A writes back, both compute 6, both store 6, and one update is silently lost. The final value is 6, not 7. The same two threads run a thousand times and usually give 7 — and then, once in a while, give 6. That intermittent, order-dependent wrongness is the signature of a race.
Races matter because they are real, common, and brutally hard to catch. They hide behind code that looks obviously correct, they appear only under certain timings, and they often vanish when you add a print statement (which changes the timing). The fix is never to hope the bad interleaving will not happen; it is to make the dangerous section indivisible by other means — a lock, an atomic instruction, or a higher-level construct. The whole field of process synchronization exists to tame race conditions.
Two threads each do count++ on a shared counter starting at 0, ten thousand times each. You expect 20000. Run it on a multicore machine without synchronization and you often get a smaller, varying number like 18742 — every lost update is one increment that two threads stepped on.
count++ is read, add, write — three steps — so two threads can lose an update.
A race that 'never reproduces' is not gone; it is just rare on your hardware. Different timing — a busy machine, more cores, a compiler change — can make it appear in production after passing every test.