Skip to main content

Case Study: Content Moderation at Scale

This case study covers designing an AI-powered content moderation system for a social platform handling millions of posts daily.

Table of Contents​


Problem Statement​

Company: Social media platform with 50M daily active users

Current state:

  • 10M posts per day
  • 500 human moderators
  • Average review time: 4 hours
  • False positive rate: 15%
  • Harmful content reaching users: 2%

Goals:

  • Reduce harmful content exposure to < 0.1%
  • Review priority content in < 15 minutes
  • Reduce false positive rate to < 5%
  • Scale without linear moderator growth

Requirements Analysis​

Content Categories​

CategorySeverityActionLatency
CSAMCriticalBlock + ReportImmediate
Violence/GoreHighBlock + Review< 1 min
Hate speechHighBlock + Review< 5 min
HarassmentMediumReview + Warn< 15 min
SpamMediumDeprioritize< 1 hour
MisinformationMediumLabel + Review< 1 hour
Adult contentLowAge-gate< 1 hour

Accuracy Requirements​

MetricTargetRationale
Recall (harmful)> 99%Minimize harm exposure
Precision> 95%Minimize false positives
Latency (critical)< 1 minPrevent spread
Latency (standard)< 15 minBalance resources

Architecture Design​

High-Level Architecture​

┌─────────────────────────────────────────────────────────────────┐
│ CONTENT MODERATION PIPELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Content │ │
│ │ Ingestion │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ TIER 1: FAST FILTERS │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Hash │ │ Keyword │ │ Known │ │ │
│ │ │ Matching │ │ Blocklist│ │ Patterns │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ Blocked │ Pass │ Elevated │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────────────────────────────┐ │
│ │ Block + │ │ TIER 2: ML MODELS │ │
│ │ Report │ │ ┌────────┐ ┌────────┐ ┌────────┐│ │
│ └─────────────┘ │ │ Vision │ │ Text │ │ Multi- ││ │
│ │ │ Model │ │ Model │ │ modal ││ │
│ │ └────────┘ └────────┘ └────────┘│ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────────────────────┼──────────────────┐ │
│ │ High Confidence │ Low Confidence │ │
│ ▼ ▼ │ │
│ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ Auto Action │ │ TIER 3: LLM REVIEW │ │ │
│ └─────────────┘ │ (nuanced cases) │ │ │
│ └────────────┬────────────┘ │ │
│ │ │ │
│ ┌───────────────────┼──────────────┐│ │
│ │ Confident │ Uncertain ││ │
│ ▼ ▼ ││ │
│ ┌─────────────┐ ┌─────────────┐ ││ │
│ │ Auto Action │ │ Human │ ││ │
│ └─────────────┘ │ Review │ ││ │
│ └─────────────┘ ││ │
│ ││ │
└──────────────────────────────────────────────────────────┘│ │

The tiered pipeline as a decision tree. Each tier escalates only what it cannot decide cheaply. The cost-per-decision ratio between Tier 1 and Tier 4 is roughly 1:5000, so getting routing right is the main lever for unit economics:

Processing Tiers​

TierMethodLatencyCostCoverage
1Hash/keyword< 10ms$0.00015% blocked
2ML classifiers< 100ms$0.00185% auto-decided
3LLM review< 3s$0.018% nuanced
4Human reviewMinutes$0.502% escalated

Classification Pipeline​

Tier 1: Fast Filters​

class FastFilters:
"""
Immediate blocking for known harmful content.
No false positives for matches.
"""

def __init__(self):
self.hash_db = PhotoDNADatabase() # CSAM detection
self.keyword_filter = KeywordBlocklist()
self.pattern_matcher = RegexPatterns()

async def filter(self, content: Content) -> FilterResult:
# CSAM hash matching (highest priority)
if content.has_media:
hash_match = await self.hash_db.check(content.media_hashes)
if hash_match:
return FilterResult(
action="block_report",
reason="csam_hash_match",
confidence=1.0,
tier=1
)

# Keyword blocklist
if content.text:
keyword_match = self.keyword_filter.check(content.text)
if keyword_match and keyword_match.severity == "critical":
return FilterResult(
action="block_review",
reason=f"keyword_{keyword_match.category}",
confidence=0.99,
tier=1
)

# Pattern matching (phone numbers in suspicious context, etc)
pattern_match = self.pattern_matcher.check(content.text)
if pattern_match:
return FilterResult(
action="elevate",
reason=f"pattern_{pattern_match.type}",
confidence=pattern_match.confidence,
tier=1
)

return FilterResult(action="continue", tier=1)

Tier 2: ML Classification​

### Tier 2: Native Multimodal Classification (Gemini 3 Flash)

```python
class MultimodalSafety:
"""
Dec 2025 Shift: No separate OCR/Vision models.
Gemini 3 Flash handles interleaved text/images natively for &lt;$0.10 / 1M posts.
"""
async def classify(self, content: Content) -> dict:
# Native multimodal understanding catches context (e.g., text on a protest sign)
response = await genai.submit(
model="gemini-3-flash",
content=[content.text, content.image_bytes],
schema=SafetySchema
)
return response

Tier 3: Nuanced LLM Review (GPT-5.2-mini)​

class NuanceReviewer:
"""
Using GPT-5.2-mini for nuanced context (sarcasm, regional slang).
Reasoning capabilities of 2025-mini models exceed 2024-frontier models.
"""
async def review(self, content: Content, context: dict) -> dict:
result = await client.chat.completions.create(
model="gpt-5.2-mini",
messages=[
{"role": "system", "content": "Analyze for regional hate speech slang."},
{"role": "user", "content": content.text}
],
response_format={"type": "json_object"}
)
return json.loads(result)

---

## Human-in-the-Loop

### Review Queue Management

Every piece of content traverses a lifecycle from submission to a terminal state. The lifecycle as a state machine makes SLOs concrete: each priority lane has a different target time-to-terminal, and an appeal can transition back to pending:

```mermaid
stateDiagram-v2
[*] --> Submitted : new post
Submitted --> Tier1 : enter pipeline
Tier1 --> Blocked : hash or keyword hit
Tier1 --> Tier2 : pass or elevate
Tier2 --> AutoAction : high confidence
Tier2 --> Tier3 : low confidence
Tier3 --> AutoAction : confident
Tier3 --> CriticalQueue : CSAM or violence
Tier3 --> HighQueue : hate speech
Tier3 --> StandardQueue : other violation
CriticalQueue --> HumanReview : SLO 15 min
HighQueue --> HumanReview : SLO 1 hr
StandardQueue --> HumanReview : SLO 24 hr
HumanReview --> AutoAction : decision logged
AutoAction --> [*]
Blocked --> Appealed : user appeals
AutoAction --> Appealed : user appeals
Appealed --> AppealQueue
AppealQueue --> HumanReview : SLO 7 days
class ReviewQueueManager:
"""
Prioritize and route content to human moderators.
"""

def __init__(self):
self.queues = {
"critical": PriorityQueue(), # CSAM, violence - immediate
"high": PriorityQueue(), # Hate speech - < 15 min
"standard": PriorityQueue(), # Other violations - < 1 hour
"appeals": PriorityQueue() # User appeals
}

async def enqueue(self, content: Content, result: ReviewResult):
priority = self.calculate_priority(content, result)

item = ReviewItem(
content_id=content.id,
content=content,
ai_analysis=result,
priority=priority,
enqueued_at=datetime.now()
)

queue_name = self.get_queue(result.severity)
await self.queues[queue_name].put(item)

# Alert if critical
if queue_name == "critical":
await self.alert_moderators(item)

def calculate_priority(self, content: Content, result: ReviewResult) -> float:
priority = 0.0

# Severity weight
severity_weights = {"critical": 100, "high": 50, "medium": 20, "low": 5}
priority += severity_weights.get(result.severity, 0)

# Reach weight (viral content prioritized)
priority += min(content.reach_score * 10, 50)

# Confidence inverse (less confident = higher priority)
priority += (1 - result.confidence) * 30

return priority

Moderator Interface​

class ModeratorDecision:
async def submit(
self,
moderator_id: str,
content_id: str,
decision: str,
reason: str,
notes: str = None
):
# Record decision
await self.store_decision({
"content_id": content_id,
"moderator_id": moderator_id,
"decision": decision,
"reason": reason,
"notes": notes,
"ai_recommendation": await self.get_ai_result(content_id),
"decided_at": datetime.now()
})

# Execute action
await self.execute_action(content_id, decision)

# Update ML models with feedback
await self.feedback_loop.record(
content_id=content_id,
ai_prediction=await self.get_ai_result(content_id),
human_decision=decision
)

Adversarial Robustness​

Evasion Techniques and Defenses​

Evasion TechniqueDefense
Character substitution (h@te)Normalization + homoglyph mapping
Image text (text in images)OCR pipeline
Invisible charactersUnicode normalization
Context manipulationMulti-turn analysis
Encoded contentDecoding pipeline
Adversarial imagesRobust vision models

Defensive Pipeline​

class AdversarialDefense:
def __init__(self):
self.normalizer = TextNormalizer()
self.ocr = OCRPipeline()
self.decoder = ContentDecoder()

def preprocess(self, content: Content) -> Content:
processed = content.copy()

# Normalize text
if processed.text:
processed.text = self.normalizer.normalize(processed.text)
processed.text = self.decoder.decode_obfuscation(processed.text)

# Extract text from images
if processed.has_images:
for image in processed.images:
extracted_text = self.ocr.extract(image)
if extracted_text:
processed.text = f"{processed.text}\n[IMAGE TEXT]: {extracted_text}"

return processed

def normalize(self, text: str) -> str:
# Homoglyph normalization
text = self.homoglyph_map(text)

# Unicode normalization
text = unicodedata.normalize("NFKC", text)

# Remove zero-width characters
text = re.sub(r"[\u200b-\u200f\u2028-\u202f]", "", text)

# Leetspeak normalization
text = self.leetspeak_decode(text)

return text

Results and Metrics​

Performance Comparison​

MetricBeforeAfterImprovement
Harmful content exposure2%0.08%96% reduction
Review latency (critical)4 hours8 minutes30x faster
False positive rate15%4.2%72% reduction
Moderator efficiency50/day200/day4x increase

Cost Analysis (Dec 2025)​

ComponentPer 10M PostsNotes
Tier 1 Filters$0.10Negligible
Tier 2 Multimodal$0.50Gemini 3 Flash ($0.05/1M)
Tier 3 LLM (GPT-5.2)$0.20Nuance checks on 10% traffic
Human Review$15.00Focused on only 1% of volume
Total$15.8040% reduction vs 2024

[!TIP] Production Wisdom: Moving the heavy lifting from 'Tier 2 Vision/OCR' to Native Multimodal (Gemini 3 Flash) reduced pipeline complexity by 70% and latency by 400ms.

Human review still dominates cost but focused on hard cases


Interview Walkthrough​

Interviewer: "Design a content moderation system for a social media platform."

Strong response:

  1. Clarify scale and requirements (1 min)

    • "What's the volume? What content types? What's acceptable false positive rate?"
    • "Any regulatory requirements (CSAM reporting, GDPR)?"
  2. Multi-tier architecture (3 min)

    • "I would use a cascade of increasing sophistication:"
    • "Tier 1: Hash matching, keyword filters - instant, certain"
    • "Tier 2: ML classifiers - fast, specialized"
    • "Tier 3: LLM review - nuanced, context-aware"
    • "Tier 4: Human review - final arbiter"
    • "Each tier handles what the previous cannot"
  3. Prioritization is key (2 min)

    • "Not all harmful content is equal. CSAM and violence need immediate action. Hate speech is priority but not instant. Spam can wait."
    • "Priority queue based on severity, reach, and confidence"
  4. Human-in-the-loop design (2 min)

    • "Humans for low-confidence decisions and appeals"
    • "AI handles 95%+ automatically to make human review economically viable"
    • "Feedback loop: human decisions improve ML models"
  5. Adversarial robustness (2 min)

    • "Users will evade detection. Defenses include:"
    • "Text normalization for obfuscation"
    • "OCR for text in images"
    • "Continuous model updates as evasion evolves"
  6. Metrics (1 min)

    • "Primary: harmful content exposure rate (target < 0.1%)"
    • "Secondary: false positive rate (user experience)"
    • "Operational: review latency, moderator throughput"

References​


Next: LLM Pricing Reference