What you'll learn
Quick Answer
Classic spam filters are statistical classifiers, with Naive Bayes as the textbook example, that learn which words show up disproportionately in spam versus legitimate messages and multiply those probabilities together to score new messages. Content moderation at platform scale is a much bigger problem that layers rule-based filters, statistical and ML classifiers, hash-matching for known bad content, and human review on top of each other, because real policy violations need context a word-frequency model alone can't capture.
Two Different Problems Wearing One Name
'Spam filter' and 'content moderation' get used almost interchangeably, but they're solving different-sized problems. A spam filter is answering a narrow, mostly binary question — is this message unwanted commercial or scam content, or not — and that question has been tractable with statistics for over two decades. Content moderation is answering a much wider set of questions: harassment, hate speech, misinformation, graphic violence, child safety, and spam as just one category among many — where the right answer often depends on context, local law, platform policy, and judgment calls a word-frequency model was never designed to make.
Both problems share a starting point, though: before you can moderate anything, you need some way to score how likely a piece of content is to violate a given category. Naive Bayes is the classic, teachable version of that scoring step, and it's still a legitimate lightweight tool today — it's just the beginning of the pipeline, not the whole system, once you're operating at platform scale.
Bayes' Theorem: The Math Underneath
Bayes' theorem gives a way to flip a conditional probability around: P(spam | words) is proportional to P(spam) × P(words | spam) — the prior probability that any random message is spam, times how likely this specific combination of words is, given that the message is spam. The filter computes this same quantity for 'ham' (legitimate mail) too, and picks whichever class comes out higher.
The 'naive' part is the assumption that, given the class, each word's presence is independent of every other word's presence. That's obviously not literally true — 'free' and 'winnings' show up together far more than chance would predict — but the classifier only needs to rank the two classes correctly relative to each other, not compute a truly correct joint probability, and in practice this simplification works well enough that Naive Bayes was the backbone of early production spam filters, with SpamAssassin and early Gmail both leaning on Bayesian scoring.
Multiplying many small probabilities together, one per word in a message, quickly underflows floating-point precision, so real implementations work in log-space — summing log-probabilities instead of multiplying raw probabilities — and use Laplace (add-one) smoothing so that a word the classifier has genuinely never seen in a given class doesn't get assigned a probability of exactly zero, which would otherwise zero out the entire product for that class no matter what else the message contains.
Building a Real Classifier in Python
Here's a real, working implementation — no external ML library, just word counts and logs, trained on a small labelled set of 18 short messages (10 spam, 8 ham):
def word_prob(word, label):
# Laplace (add-1) smoothing
return (class_word_counts[label][word] + 1) / (total_words[label] + V)
def classify(text):
words = tokenize(text)
log_scores = {}
for label in ("spam", "ham"):
log_p = math.log(prior[label])
for w in words:
log_p += math.log(word_prob(w, label))
log_scores[label] = log_p
return log_scoresword_prob adds 1 to every word's raw count and adds the vocabulary size V to the denominator — the standard Laplace smoothing formula — so an unseen word gets a small but nonzero probability instead of a hard zero. classify sums log-probabilities across every word in the message instead of multiplying raw probabilities, sidestepping the underflow problem entirely.
Classifying Real, Unseen Messages
The training set behind this run gives priors of P(spam) = 0.5556 and P(ham) = 0.4444, over a vocabulary of 98 distinct words seen across both classes. Three messages the classifier never saw during training, run through classify() for real, with the actual computed probabilities:
"free cash prize click now to claim your winnings"
-> P(spam) = 0.999961 P(ham) = 0.000039 => SPAM
"are you free for lunch tomorrow to catch up"
-> P(spam) = 0.035661 P(ham) = 0.964339 => HAM
"urgent click here to verify your account now"
-> P(spam) = 0.999826 P(ham) = 0.000174 => SPAMThe middle example is the interesting one. 'Free' appeared repeatedly in the spam training messages ('free iphone,' 'free gift card'), so on its own it would push toward spam — but the second message classifies confidently as ham anyway, because Naive Bayes sums the evidence from every word in the message together in log-space, not just one word. 'Lunch,' 'tomorrow,' and 'catch up' pull hard enough toward ham to overwhelm the one word that looks spammy in isolation. That's the actual mechanism working as intended, not a lucky guess.
Why Modern Content Moderation Outgrows Bayes
A bag-of-words model like this is trivially evaded once someone is deliberately trying to get past it: replacing letters with look-alike numbers or symbols, adding random spacing or punctuation inside words, using homoglyphs (visually identical characters from other unicode blocks), or putting the actual spam text inside an image instead of as text at all. Real content moderation systems have had to layer several different kinds of defense on top of statistical text classifiers for exactly this reason:
- Hash-matching for known bad content — perceptual hashing fingerprints known violating images and videos so that re-uploads and near-duplicates can be caught instantly, without needing to re-run any classifier at all.
- Larger, continuously retrained ML models — modern systems increasingly use transformer-based text and multimodal classifiers trained on far larger, constantly-updated datasets that pick up context, sarcasm, and coded language a static word-frequency table misses entirely.
- Behavioral and graph signals — account age, posting velocity, the network of who follows or messages whom, device and IP fingerprints — signals that catch coordinated abuse campaigns even when each individual message's text looks completely unremarkable on its own.
- Human review — for genuinely ambiguous cases (satire versus harassment, news reporting versus glorifying violence, cultural or regional context a model has no way to know), platforms route to trained human reviewers rather than letting an automated score make the final call.
None of this replaces the statistical layer — it's usually still there, doing the first, cheap pass on the overwhelming majority of content that's clearly fine or clearly not — it just stops being the last word on anything genuinely contested.
The Precision/Recall Trade-off Nobody Escapes
Every classifier used for moderation sits on a threshold, and moving that threshold trades two different kinds of error against each other. Precision asks: of everything the system flagged, how much was actually bad? Recall asks: of everything that was actually bad, how much did the system actually catch? Turning the threshold up (stricter) improves precision but lets more real violations slip through unflagged; turning it down (looser) catches more real violations but also flags more innocent content by mistake.
At the scale a major platform operates, this trade-off has real teeth in both directions. A false-positive rate of even 0.1% sounds tiny, but against billions of daily messages it's an enormous absolute number of real users wrongly muted, flagged, or banned — which is why every serious moderation system pairs its automated score with an appeals path rather than treating the model's output as a final verdict. A false-negative rate that looks equally small translates into real, uncaught harm reaching real people, which is the cost on the other side of the same dial.
There's no threshold setting that eliminates both kinds of error at once for a fixed model — improving one, without the model itself getting better, means accepting more of the other. The actual engineering work in content moderation is mostly about pushing the whole trade-off curve outward through better models, better signals, and better context, rather than just sliding along a fixed one.
