SysDesignPrep.com
System design interview question

Design a Content Moderation System

Catch spam, nudity, hate and illegal media in 4 billion daily posts and comments before they spread, with 15,000 reviewers for the hard cases.

Last updated 2026-10-27. Difficulty: hard. Patterns: moderation, perceptual-hashing, review-queues, policy-engine, audit-log. Reported at Meta, TikTok, Google, Reddit, Discord, Snap.

Walk through a strong candidate's answer, turn by turn.

The interviewer asks, the candidate answers and draws, and you press Next. Skip to any stage or key moment, and ask the AI Mentor anything along the way.

Or sit it in the style of Meta · TikTok · Google · Discord, run as that company’s round is reported to go.

The short answer

How to design Content Moderation System in a system design interview, in five steps:

  1. Scope it. Check every post, comment, image and video, block known violating media at upload, gate distribution on confidence, user reports, human review queues, graduated actions, appeals, policy changes without model changes, re-scans, legal reports and transparency.
  2. Set the targets. Write-time checks: p99 < 50 ms added; Reach before review: limited until cleared · images p95 < 1 min · video p95 < 10 min; Known illegal imagery: 0 views of a hash-list match; Time to action by severity: S0 < 15 min · S1 < 1 h · S2 < 24 h.
  3. Do the numbers. Items written per second: ~46 k/s avg · ~120 k/s peak; Inline text model at peak: ~30 GPUs; GPUs for video scanning: ~350 busy · ~700 provisioned.
  4. Draw the design. Inline checks, API gateway, Content service, Policy engine, Hash matcher, Event log, Async scanners, Review platform, Enforcement service.
  5. Go deep where it is hard.
    • Cheap checks inline, heavy models before reach → Tiers by cost plus reach states: inline checks decide acceptance and reach, heavy models run after the ack, uncertain items stay limited until cleared.
    • Perceptual hashes, adversaries and re-scans → Perceptual hashes (PDQ for images, TMK+PDQF for video) with per-list radii, a copy-detection embedding for heavy edits, and every upload’s hashes stored for re-scans.
    • Scores are not decisions: the policy engine → Calibrated per-label scores in; a versioned, declarative policy evaluated per surface, country and viewer; actions out.
    • Review queues: expected harm, routing, one decision per cluster → Expected-harm priority with severity deadlines, routing by policy and language, one case per cluster, and quality control built in.
    • Reviewer decisions as training labels → Policy-versioned labels from every decision, weighted by agreement, plus an active-learning slice and a random prevalence sample.
    • The enforcement ledger: strikes, appeals, reports → Append-only ledger written first; visibility, reach, standing, notices and reports applied as idempotent projections.

Functional requirements

  • Check every post, comment, image and video. Spam, adult nudity, hate, harassment, graphic violence, violent extremism and known illegal imagery. Cheap checks inside the write; heavier image, video and multimodal models after the post is accepted and before it reaches a wide audience.
  • Block known violating media at upload. Match against hash lists (industry lists of known child sexual abuse material and terrorist content, plus our own lists of media removed before), surviving re-encoding, resizing and small edits.
  • Gate distribution on confidence. An item whose scan is pending or whose score is uncertain reaches existing followers only (no recommendations, search or trending) until it is cleared. A slow check costs reach, not safety.
  • User reports. 2 M a day, folded into one case per item and per cluster of copies, weighted by how often each reporter’s past reports were upheld. Notices from trusted flaggers get priority.
  • Human review queues. Ordered by expected harm, routed by policy and language, with deadlines by severity. One decision per cluster of copies, applied to all of them. Reviewer protections and quality control are part of the tool.
  • Graduated actions. Remove, withhold in a country, limit distribution, age-gate, warning screen, strike, feature limits, suspension. Each comes with a notice and a statement of reasons.
  • Appeals. By the author, and by reporters when content is left up. Decided by someone other than the first reviewer, and by a person when the first decision was automated. A reversal restores the content and removes the strike.
  • Policy changes without model changes. Thresholds and actions are versioned per policy, surface and country, replayed over stored scores and shadowed before they take effect.
  • Re-scans, legal reports and transparency. Re-scan existing content when a hash list or policy changes; file the reports the law requires (for example to NCMEC in the US); publish transparency reports from the same records.
  • Out of scope. Model architectures and training code, fake-account and bot detection (used here only as reputation signals), ads review, live streams, end-to-end encrypted messages, fact-checking programmes, and reviewer workforce scheduling.

Non-functional requirements

  • Write-time checks (p99 < 50 ms added). They run on every one of ~120 k items a second at peak, so only cheap checks go inline: limits, reputation, hash lookups and a small text model. Anything that needs a GPU for longer runs after the ack.
  • Reach before review (limited until cleared · images p95 < 1 min · video p95 < 10 min). This drives the hardest trade-off in the design: the models that catch the most are too slow and costly to run inline, so uncertain items wait at reduced reach instead of blocking the post or spreading unchecked.
  • Known illegal imagery (0 views of a hash-list match). Blocked inside the write, preserved and reported where the law requires. General reviewers never see it; a specialist team handles confirmations.
  • Time to action by severity (S0 < 15 min · S1 < 1 h · S2 < 24 h). Measured from the first signal (report or detection) to the action, and as views before action. Exposure is what the user feels, so it is the number reported alongside latency.
  • Precision of automated actions (≥ 98 % per policy). Measured weekly from blind second reviews and appeal reversals. A policy whose automated removals fall below it routes to review instead of acting.
  • Every decision recorded (100 % in the ledger · version + evidence). Appeals, strikes, statements of reasons, legal reports and transparency counts all come from one append-only record, so they cannot disagree.
  • Fail to limited, not open or shut (posting up when checks are down). A moderation outage must neither stop posting nor let unscanned items into recommendations. Missing answers mean limited reach.
  • Reviewer wellbeing and privacy (exposure caps enforced in the tool). Blurred, greyscale previews by default and per-shift caps on the most severe queues. Reviewers see only the personal data a decision needs, and every access is logged.

Back-of-envelope estimates

  • Items written per second: ~46 k/s avg · ~120 k/s peak. 1 B posts + 3 B comments = 4 B items/day. 4 B ÷ 86 400 ≈ 46 k/s; evenings and big events run ~2.5× that, ~120 k/s. Every one passes the inline checks, so that tier is sized for the peak with a 50 ms budget.
  • Inline text model at peak: ~30 GPUs. A small distilled multilingual classifier batched every few milliseconds does ~4 k short texts a second per GPU. 120 k/s ÷ 4 k = 30 GPUs at peak, ~12 on average. Hash lookups add ~6 k/s at peak, a few cores. The inline tier is cheap; its constraint is the 50 ms deadline, not money.
  • GPUs for video scanning: ~350 busy · ~700 provisioned. 10 M videos × ~1 min average × ~3 GPU-seconds per minute (frames at 1 fps, speech to text, a multimodal model on flagged segments) = 30 M GPU-s/day ≈ 8,300 GPU-hours ≈ 350 GPUs busy on average, ~700 to absorb peaks. At ~$2 per GPU-hour that is ~$17 k a day. Images (100 M × 20 ms ≈ 23 GPUs) and text are small next to it: video is 0.25 % of items and ~85 % of the GPU bill.
  • Hash lists in memory: ~1.6 GB raw · ~29 GB indexed. ~50 M list entries × 32 B (a 256-bit PDQ hash) = 1.6 GB. The multi-index keeps 16 chunk tables, each entry stored with its full hash for a sequential scan: 16 × 50 M × 36 B ≈ 29 GB, held in RAM on every matcher. A lookup reads 16 buckets of ~760 entries, ~0.1 ms, so RAM rather than CPU is the cost.
  • Review capacity vs demand: ~2.6 M decisions/day vs ~20 M candidates. 15,000 reviewers × 5/7 on shift ≈ 11,000 a day × 6 h on queue × ~40 decisions an hour ≈ 2.6 M/day. Candidates: ~0.5 % of 4 B items land in an uncertain band (~20 M), plus 2 M reports on ~0.8 M items and ~250 k appeals. People can look at about one candidate in eight, so the queue is a ranking and the rest stay limited.
  • Enforcement ledger: ~45 M rows/day · ~16 TB/year. ~20 M automated actions a day (0.5 % of items, mostly spam), ~20 M holds kept because of a score, and ~2.6 M human decisions ≈ 45 M decisions × ~1 KB = 45 GB/day, ~16 TB/year. Kept for years because strikes, appeals, legal requests and transparency reports read it. Evidence (preserved copies) is stored apart, with its own retention.
  • Stored hashes for re-scans: ~600 B hashes · ~19 TB. Every upload’s hashes are stored: 100 M images + 10 M videos × ~10 keyframes = 200 M hashes/day. Over 8 years ≈ 584 B × 32 B ≈ 19 TB. A list update streams these through an index of only the new entries: one sequential pass, under an hour on a few hundred cores, instead of re-reading exabytes of media.
  • Re-scoring with a new image model: ~12 days on 200 GPUs. ~10 B images were viewed in the last 30 days and carry nearly all views. 10 B × 20 ms = 200 M GPU-s ≈ 55 k GPU-hours ≈ 12 days on 200 GPUs, ~$110 k. All ~300 B stored images would take 30× longer, so backfills run by exposure, newest and most viewed first.

Components

  • Inline checks (limits · reputation · text · hashes): The cheap tier, run inside the write: rate limits scaled by account reputation (age, verified phone, prior strikes, upheld reports), text normalised (Unicode NFKC, look-alike characters, zero-width characters) and scored by a small multilingual model in ~5 ms on batched GPUs, and a hash lookup per media file. ~120 k items a second at peak on ~30 GPUs. It returns signals and scores, never actions: the policy engine decides.
  • Apps (post · report · appeal): The mobile and web apps. Media goes to object storage through a presigned upload before the post is sent, and the upload pipeline computes each file’s SHA-256 and perceptual hashes, so a post carries media ids rather than bytes. The apps also file reports and appeals and show notices with the statement of reasons for any action.
  • API gateway (auth · per-IP limits): Authenticates users and applies coarse per-IP and per-device limits against floods, then routes posts and comments to the content service, and reports and appeals to the review platform. It holds no moderation logic, so policy never depends on which client sent the request.
  • Content service (sharded by item id · outbox): Source of truth for posts, comments and media metadata, including each media file’s stored hashes and every item’s visibility. On a write it calls the inline checks with a 50 ms deadline, then commits the item, its reach state and an item.created event in one transaction. If the checks time out, the item is accepted as limited rather than rejected, except for authors with low reputation, whose items are held until checked.
  • Policy engine (versioned rules · score store): Turns calibrated per-label scores plus context (surface, country, viewer age, account signals, reports, hash matches) into a decision under an immutable, versioned policy: an action and its scope, a reach state, and whether a person must look, with severity and queue. It keeps each item’s latest scores and model versions (~150 B per item for 90 days), so a threshold change is replayed over stored scores instead of rerunning models. Models never contain thresholds.
  • Hash matcher (PDQ multi-index · ~50 M in RAM): Every list of known violating media in memory: industry lists of known child sexual abuse material and terrorist content, and our own lists of media removed before, ~50 M 256-bit PDQ hashes plus SHA-256 and video signatures. Each hash is split into 16 chunks of 16 bits with one table per chunk, so a near-match within 15 bits takes ~0.1 ms and within 31 bits about a millisecond. Each list carries its own match radius and action, and hashes of near-blank images are never matched.
  • Event log (Kafka · key = item id): item.created and item.edited events from the content service’s outbox, keyed by item id so an item’s versions stay in order. Scanners consume it in separate lanes for hashing, images, video and backfill, so a re-scan never delays fresh uploads. Everything slow sits behind it, so posting latency never depends on a GPU queue.
  • Async scanners (GPU · image, video, OCR, multimodal): The heavy tier, after the ack and before wide reach: image models (~20 ms of GPU each), OCR so text drawn into pictures is scored, video at ~3 GPU-seconds per minute (a cheap per-frame model at 1 fps, speech to text, then a multimodal model on flagged segments), and a model over image and caption together. Video is 0.25 % of items and ~85 % of the ~400 busy GPUs. It also places each item in a near-duplicate cluster using an index of the last 7 days of uploads.
  • Review platform (queues · routing · tool · QC): Folds reports and policy referrals into one case per item or cluster, weights reports by reporter reliability, and orders each (policy, language) queue by expected harm with deadlines by severity. Serves cases to reviewers certified for that policy and language in a tool with blurred, greyscale previews and per-shift exposure caps, and mixes in known-answer items and blind second reviews. Appeals are cases too, never given to anyone who decided the item before.
  • Enforcement service (ledger first · idempotent effects): The only writer of enforcement. It records each decision in the ledger first, then applies its effects keyed by decision id: visibility in the content service, reach state, account standing, notices with a statement of reasons, banked hashes and legal reports. A reconciler replays the ledger to repair any effect that failed, and the service serves decision records to the review platform for appeals.
  • NCMEC & regulators (reports · lists · statements): Outside bodies: NCMEC, to which US providers must report apparent child sexual abuse material they become aware of; industry hash-sharing lists; the EU Commission’s database of statements of reasons under the Digital Services Act; and authorities that send removal orders. Their rules set deadlines, preservation periods and what the ledger must be able to prove.
  • Training & re-scans (labels · active learning · backfills): Turns ledger decisions into policy-versioned labels, picks the active-learning slice and the random prevalence sample for review, retrains and calibrates models, and runs re-scans: new hash list entries against ~600 B stored hashes (~19 TB, under an hour), and new models over recently viewed media first within a GPU budget. Transparency counts are computed here from the ledger.
  • Enforcement ledger (append-only · by day · hash-chained): One row per enforcement decision: subject, action and scope, policy id and version, source, model versions and scores, hash list entry and distance, pseudonymous reviewer id, evidence pointer, and the parent decision for appeals and reversals. ~45 M rows a day, ~16 TB a year, never updated: a reversal is a new row. Strikes, statements of reasons, transparency counts and training labels are all computed from it.
  • Reach gate (in-memory KV · per item version): Per item version: eligible, limited (followers and direct links only, with a 10 k impression cap for authors over 100 k followers), restricted (age-gated or behind a warning screen, never recommended), withheld in listed countries, or removed. Recommendation and search indexes follow its change stream, and every recommended page is checked against it in one batched read before serving. A missing or unreadable key reads as limited, so a failure costs reach, not safety.
  • Feeds, search, recs (outside this design): Every surface that hands an item an audience: followers’ feeds, recommendations, search, trending and hashtag pages. They run no moderation of their own: they index only eligible items and check each page of candidates against the reach gate, dropping anything not eligible for this viewer’s country and age.

User flows

  1. A photo post, checked inline and cleared. The main write path. Cheap checks run inside the request and decide whether the post is accepted and at what reach; the image models run after the ack, and the post becomes eligible for recommendations only when they clear it. The author never waits for a GPU, and nothing unscanned reaches a stranger’s feed.
    1. The app posts a photo it has already uploaded, with a caption; the gateway authenticates the author and forwards the post to the content service. The photo went to object storage through a presigned upload before the post was sent (see Design Instagram), and that step computed its SHA-256 and its PDQ hash with a quality score. So the post carries a media id, and the inline checks never touch pixels. Hashing once at upload is also what makes re-scans cheap later.
    2. The content service calls the inline checks with the author, the text and the media hashes, under a 50 ms deadline. Cheapest first: rate limits and the author’s reputation (account age, verified phone, prior strikes, report history) from an in-memory store, in microseconds. A day-old account posting its thirtieth link in an hour stops here, before any model runs. Limits scale with reputation, so a new account gets a few posts an hour with links and an established one far more.
    3. The checks normalise and score the caption, and look up the photo’s hashes in the hash lists. Normalisation comes before the model: Unicode NFKC, look-alike characters mapped to one form, zero-width characters removed, repeated letters collapsed, links resolved to their final domain. A small multilingual model then scores spam, hate and harassment in ~5 ms on batched GPUs. The hash matcher answers an exact SHA-256 hit or a PDQ near-match in about a millisecond; a match on a list of known child sexual abuse material stops the post here, never visible, preserved and reported (the re-scan flow shows that path).
    4. The policy engine turns the scores into a write-time decision for this surface and country. Scores say how likely each label is; the policy says what to do about it. Every score here is under its action line, but the image models have not seen the photo yet, so the decision is accept at limited reach. A text-only post with clean scores from an author in good standing gets eligible at once from the same engine.
    5. The content service commits the post with its reach state and an outbox event, then acknowledges the author. The item row, its reach state and the item.created event commit in one transaction, so a crash cannot leave a post that is public and never scanned. Inline checks added ~20 ms typically and 50 ms at worst. If they time out, the post is still accepted as limited, unless the author’s reputation is low, in which case it is held until checked.
    6. The image scanners classify the photo, read any text in it, and score the photo and caption together. Nudity, graphic violence and hate-symbol models share one decode of the image. OCR pulls out drawn-in text so the text model sees it, and a multimodal model scores the image and caption together, because a harmless photo with a hateful caption is exactly what each model alone misses. About 20 ms of GPU per image; p95 under a minute end to end, mostly queueing.
    7. The scanners send the scores to the policy engine, which decides the post is clear. Every score is under its clear line for this surface and country, so the decision is eligible. The scores are stored with their model versions, so a later threshold change can be replayed over them without running a model again.
    8. The enforcement service sets the post’s reach to eligible, and recommendations and search can now serve it. Recommendation and search indexes pick up the change from the gate’s change stream, and the gate is also the final filter at serve time: each page of candidates is checked in one batched read, so a stale index cannot serve something that was limited a second ago. A clearance is not an enforcement action and is not written to the ledger; a hold kept because of a score is.
  2. A reported comment reaches the right reviewer. The main human path: a hateful comment in Portuguese under a popular post is reported 40 times. The reports become one case, ordered by expected harm, routed to a reviewer certified for hate speech in Portuguese, decided once, and recorded before anything changes.
    1. Users report the comment; the gateway sends each report to the review platform, which folds them into one case. Forty reports on one item are one case, not forty. Each report is weighted by its reporter’s record (the share of their past reports that were upheld), so a coordinated mass report moves priority less than one notice from a trusted flagger, which the EU Digital Services Act requires platforms to handle with priority.
    2. The review platform gets the comment’s stored scores and the policy’s view of severity and queue. The comment scored 0.55 for hate at write time, inside the uncertain band, so it was already limited; the reports raise the estimated probability of a violation to about 0.7. If the comment’s cluster had already been decided, the platform would apply that decision without a person.
    3. The case is placed in the Portuguese hate-speech queue, ordered by expected harm. Priority is P(violation) × severity weight × predicted views in the next 24 hours. Here 0.7 × 10 (S2) × ~40 k views from a busy thread ≈ 280 k, far ahead of a likely-spam comment at 0.9 × 1 × 50 ≈ 45. Priorities are recomputed every few minutes from view velocity, and as a case nears its severity deadline the deadline takes over.
    4. A reviewer certified for hate speech in Portuguese opens the case in the review tool. The tool shows the comment, the thread around it, the author’s recent history in summary, the policy text for the version in force, and the model’s scores. Media in a case would be blurred and greyscale until the reviewer chooses to look. The tool counts each reviewer’s exposure to the most severe queues and stops serving those cases once the shift’s cap is reached.
    5. The reviewer decides it violates the hate policy, and the verdict goes to the enforcement service. The verdict names the exact rule, not just "hate", because the statement of reasons, the strike weight and the training label all depend on it. About 5 % of verdicts are picked at random for a blind second review before they are final, and 3 % of the queue is known-answer items the reviewer cannot tell apart.
    6. The enforcement service records the decision in the ledger before applying anything. Ledger first, then effects: an effect without a record can never be explained, appealed or counted, while a record whose effect failed is repaired by retrying. The row holds the rule and policy version, the scores, the reports, the reviewer’s pseudonymous id and a pointer to a preserved copy.
    7. It removes the comment, records a strike against the author, and sends a notice with a statement of reasons. Every effect is keyed by the decision id, so a retry cannot apply it twice. The author’s standing is recomputed from upheld decisions in the last 90 days, weighted by severity. The notice says what was restricted, which rule and facts it rests on, whether automated means were used, and how to appeal, which is what a statement of reasons under the DSA needs. Reporters are told the outcome.
    8. The decision flows from the ledger to training as a label tagged with its policy version. A label is only as valid as the definition it was made under, so it carries the rule and the policy version, plus whether it was a second review or later reversed on appeal. The comment’s text, normalised, also joins the near-duplicate index, so copies posted under other threads join its cluster and are decided without another review.
  3. One violating video, 100,000 copies. The scale-breaking case: a graphic video is uploaded, then re-uploaded by thousands of accounts, some edited to evade matching. Reviewing each copy would take more reviewer hours than exist. The design reviews it once, applies the decision to every copy, and blocks future copies at upload.
    1. A new video passes the inline checks, is accepted at limited reach and queued for the video scanners. It matches no list yet, the caption is clean and the account is not new, so nothing inline objects. Limited reach means its followers can watch but no recommendation, search or trending surface can, so for the next few minutes its audience is bounded by the uploader’s followers.
    2. The video scanners score its frames, speech and segments, and find likely graphic violence. A cheap frame model at 1 fps finds the segments worth a closer look, and only those go to the expensive multimodal model, which is how video stays near 3 GPU-seconds per minute. The scanners also store the video’s TMK+PDQF signature and keyframe PDQ hashes, and its cluster already has 3,000 members from other accounts in 20 minutes.
    3. The policy engine keeps every copy limited behind a warning screen and sends the cluster to review at top priority. Predicted reach is the cluster’s, not one copy’s: 3,000 copies gaining views is a viral item, so it lands at the top of a specialist queue with the S1 deadline of an hour. The warning screen is applied to the whole cluster meanwhile, because 0.91 is high but not above the line for automatic removal in this policy.
    4. A specialist reviewer decides the cluster once: it violates the graphic violence policy. The reviewer sees one representative copy as a strip of blurred, greyscale keyframes with the flagged segments marked and audio off, not as playback. The verdict applies to the cluster. Members far from the representative (a wider hash distance or only an embedding match) are listed as a grid for a quick spot check rather than reviewed one by one.
    5. The enforcement service records a decision for every copy, removes them all, and banks the video’s hashes. One verdict fans out to one ledger row per copy, each pointing to the parent decision, because each author gets their own notice, strike and right to appeal. At ~10 k items a second the fan-out takes seconds even for 100 k copies. The signatures go into an internal list with action block at a tight radius and review at a wider one.
    6. New uploads of the video now match the banked hashes inside the write and are blocked before anyone sees them. Blocking at upload is the main lever in a re-upload wave: Facebook said it removed about 1.5 million copies of the Christchurch attack video in the first 24 hours, more than 1.2 million of them at upload. Video hashes come from the upload pipeline within seconds; if they are not ready when the post arrives, the hash lane runs first in the scanners, so a match lands within seconds either way.
    7. Edited copies that miss the tight radius join the cluster through the wider radius or the embedding, and go to review as members. Crops, mirroring, borders, overlaid text and speed changes move a perceptual hash further than re-encoding does. Mirrored and rotated variants are hashed too, and a learned copy-detection embedding catches heavier edits. Those copies are confirmed in batches of twenty as members of a decided cluster, seconds each, instead of opening new cases.
  4. A wrong removal, appealed and reversed. The failure path: the image model removes a breast cancer awareness post as adult nudity. The author appeals; a person who was not part of the first decision reviews it, the reversal is a new ledger row, and everything derived from the first decision (visibility, reach, the strike, the counts, the label) follows it.
    1. The image model scores a post 0.985 for adult nudity, above the automatic removal line, and the post is removed. Adult nudity is removed automatically at 0.98 and above on this surface, where measured precision is about 99 %. At ~100 k such removals a day, 1 % is still ~1,000 wrong removals daily, which is what appeals exist for. The ledger row records the model version, the score, the threshold and the policy version, so the error can be traced exactly.
    2. The author gets a notice with the statement of reasons and appeals; the appeal reaches the review platform. The notice says what was removed, under which rule, that the decision was automated, and how to appeal. Under the DSA, an internal complaint system must be available for at least six months after a decision, and complaints must not be decided solely by automated means.
    3. The review platform builds an appeal case from the decision record and excludes everyone who decided the item before. The enforcement service returns the whole decision chain from the ledger: scores, rule, version, evidence. Every reviewer in that chain is excluded, and the case goes to a more senior certification at another site where possible. The first decision was automated, so this is the first time a person sees the post.
    4. A senior reviewer finds the post allowed under the health-awareness exception and reverses the decision. The reviewer sees the rule as it was when the post was removed and as it is today; either one would be enough to restore it. The verdict names the exception, which matters later: it tells the policy team and the model that this is a known context, not a one-off.
    5. The enforcement service appends a reversal row, then restores the post, its reach and the author’s standing. The first row is never edited; the reversal points to it as its parent. Standing is recomputed without the strike, the post goes back to the reach it would have had, and the author is told. Transparency counts will show the item as removed and restored on appeal, both dated by their own decisions.
    6. The reversal becomes a high-weight training label, and repeated reversals in one context flag the threshold. An appeal that overturns a confident automated decision is the most valuable label there is: a measured model error. Reversal rate per rule and per model version is a headline metric, and if health-awareness posts keep being reversed, the policy team adds a rule that routes posts with that context signal to review instead of removing them, while the next model learns the case.
  5. A hash list update reaches old content. The background path: an industry list adds 20,000 hashes of known illegal imagery. Copies already on the platform must be found without re-reading exabytes of media; the hashes stored at upload turn it into one pass over ~19 TB. Matches are removed, preserved and reported, and the reports and counts come from the ledger.
    1. The enforcement service pulls a list update from an industry partner and loads the new entries into the hash matcher. Each list has an owner, a version and a policy: here, block at a tight radius and route a wider one to a specialist queue. New entries are appended to the matcher’s chunk tables on every replica, so new uploads match from the moment the load finishes, within a minute.
    2. The re-scan job streams every stored media hash through an index built from only the new entries. ~600 B stored hashes, ~19 TB, are read sequentially in the scanners’ batch lane. The 20,000 new entries make a small 16-chunk index that sits in cache, so each stored hash costs 16 lookups. One pass takes under an hour on a few hundred cores and touches no media at all.
    3. Candidates are confirmed against the original files and the policy engine decides. Stored hashes came from the upload; the batch lane re-hashes the original file at full resolution and runs the image model as a second signal, because a severe action on an old account deserves more than one stored number. Tight matches confirmed this way are removed and reported; the rest go to the specialist queue.
    4. The enforcement service records each match, removes the media everywhere, and preserves a copy for the report. Removal means every surface and every cached copy: visibility in the content service, the reach gate, and a CDN purge of the media’s URLs. The preserved copy goes to a restricted evidence store with every access logged, separate from the ledger, which holds only a pointer to it.
    5. The enforcement service files the report the law requires and records that it did. US providers must report apparent child sexual abuse material they become aware of to NCMEC’s CyberTipline and preserve it for a period the law sets, which a 2024 law extended to a year. Whether a person at the provider views a hash-matched image before it is reported has legal consequences (US courts have split on whether investigators may then open a file nobody at the provider viewed without a warrant), so the design supports both and counsel decides. The report id goes into the ledger.
    6. Each period, the transparency job counts decisions from the ledger by policy, source and outcome. Proactive rate (found by us before any report), appeals, restorations, legal reports and authority orders are all queries over the same rows that produced each user’s notice, dated by decision time, so the aggregate and the individual answers cannot disagree. The same rows feed the statements of reasons the DSA asks platforms to send to the Commission’s public database.

Deep dives

Cheap checks inline, heavy models before reach

Why not run your best models on every post before it is published? And if you don’t, what stops a bad video spreading while it waits?

Cost and latency differ by four orders of magnitude across the checks. Rate limits, reputation and a hash lookup cost microseconds; a small text model about a quarter of a millisecond of GPU; an image model ~20 ms; video ~3 GPU-seconds per minute of footage and minutes of wall clock for a long upload. At ~120 k items a second at peak the write has a 50 ms budget, and video is 0.25 % of items but ~85 % of the ~400 GPUs the scanners keep busy. Running everything inline would make every post wait on the slowest model and turn a GPU shortage into a posting outage.

Harm is not a property of a post alone; it is the post times the people who see it. A new post reaches almost nobody in its first minutes unless the system hands it an audience through recommendations, search or trending, and those are exactly the surfaces we control. So a check that is still running does not have to mean unsafe or invisible: it can mean a smaller audience until it finishes.

  • Tiers by cost plus reach states: inline checks decide acceptance and reach, heavy models run after the ack, uncertain items stay limited until cleared chosen
  • Run every model inline before publishing rejected
  • Publish fully, scan afterwards, take down what fails rejected
  • Hold items invisible until every check passes situational: for accounts with low reputation or recent strikes, for ads, and for slots that give an item a large audience at once, such as a featured spot

The answer: Three tiers by cost: inline (limits and reputation, exact and perceptual hash lookups, normalised text through a small multilingual model; ~20 ms typical, 50 ms deadline), async (image, OCR, multimodal and video models; images p95 under a minute, video under ten), and human review. Every item version gets a reach state: eligible (any surface), limited (followers and direct links, no recommendations, search or trending, and a 10 k impression cap for authors with more than 100 k followers until cleared), restricted (age-gated or behind a warning screen, never recommended), withheld in listed countries, or removed. Text from an author in good standing with clean inline scores is eligible at once; media is limited until its scan clears, and anything in an uncertain band stays limited until a person clears it. Surfaces check the gate in one batched read per page and treat a missing key as limited, so a slow, failed or lagging scan can only cost reach. The TikTok question on this site uses a staged audience for a related reason; here the gate is driven by uncertainty about policy, not by how well the item performs.

The video scanners fall an hour behind. What happens?

New videos stay limited longer: followers can watch, recommendations cannot. Nothing unscanned leaks out and posting is unaffected. The backlog drains by expected reach rather than first in, first out, so a video from a 5 M-follower account that is gaining views is scanned before a clip with 20 views, and the hash lane has its own pool so known material is still caught in seconds. The alert is scan lag by reach band, and the cost of the incident is the views that waited.

An account with 80 M followers posts a video. Isn’t followers only already wide distribution?

Yes, which is why limited means an impression cap for large accounts: 10 k until cleared. Their uploads also go to the front of the scan queue, so the cap rarely bites for long. Reputation lowers their prior but never skips the scan, and if their video lands in the uncertain band it goes to review near the top, because its predicted reach is enormous.

Someone posts a harmless photo, waits for it to be cleared, then edits it.

An edit is a new version, and a new version goes back through the inline checks and back to limited until the scanners clear it. The reach gate is keyed by item and version, so an eligible state cannot carry over to new content. Swapping media, changing a caption and adding a link all count as edits.

The reach gate is unavailable. Do surfaces fail open?

No. A key that cannot be read counts as limited, so recommendations and search drop the candidates they cannot verify and fill the page with others. Followers’ feeds keep working, because limited items are allowed there anyway. What the outage costs is freshness in recommended content, which is the right side to fail on.

How do you know gating isn’t costing creators too much?

Measure it: time from post to eligible by media type and percentile, the share of items still limited after an hour, and the views those items would have had, estimated from cleared posts by similar authors. Put that next to views before action for items later removed. Where a segment pays a large hold for little catch, such as text from long-standing accounts, the policy makes it eligible at once.

Perceptual hashes, adversaries and re-scans

Why not just keep a set of SHA-256 hashes of known violating images? And when a list gains 20,000 entries, how do you find the copies already on the platform?

A cryptographic hash changes completely when one pixel changes, and every re-encode, resize or screenshot changes many. People spreading known violating media re-encode, crop, mirror, add borders and overlay text on purpose. A perceptual hash summarises what an image looks like instead: PDQ, which Meta open-sourced in 2019 together with the TMK+PDQF video algorithm, turns an image into 256 bits, and two copies of the same picture differ in a handful of bits while unrelated images differ in roughly half. Microsoft’s PhotoDNA is the long-standing example many platforms use against child sexual abuse material.

Matching becomes a distance threshold, and every threshold trades misses for false matches: a tight one misses a cropped copy, a wide one matches unrelated images that share structure, such as near-blank frames or one meme template with different captions. And the platform already holds ~300 B images plus video. Re-reading and re-hashing them for each list update means exabytes; storing each item’s hashes once at upload turns the re-scan into reading ~19 TB.

  • Perceptual hashes (PDQ for images, TMK+PDQF for video) with per-list radii, a copy-detection embedding for heavy edits, and every upload’s hashes stored for re-scans chosen
  • Cryptographic hashes only (SHA-256 or MD5) situational: as the first, free check: an exact copy matches with certainty, and some shared lists include MD5s
  • Learned embeddings with nearest-neighbour search only situational: as a second layer for edited copies of high-severity clusters, behind the hash
  • Re-download and re-hash all stored media on each list update rejected

The answer: At upload the media pipeline computes SHA-256, PDQ with its quality score and the mirrored and rotated variants, and for video a TMK+PDQF signature plus PDQ on ~10 keyframes, and stores them with the item. The matcher holds ~50 M list entries in RAM as a multi-index: each 256-bit hash split into 16 chunks of 16 bits, one table per chunk. Two hashes within 15 bits must agree exactly on at least one chunk, so a lookup is 16 bucket reads and ~12 k candidate comparisons, about 0.1 ms; out to 31 bits, probing each chunk’s one-bit neighbours is ~17× the work, about a millisecond. Each list carries its own policy: an industry list of known illegal imagery blocks within 15 bits and sends 16 to 31 to a specialist; our own lists of previously removed media route to review, because context decides whether a repost violates. Hashes below a quality score are never matched. A list update loads into the matcher within a minute, then a re-scan streams all ~600 B stored hashes through an index of just the new entries, under an hour on a few hundred cores, and confirms candidates from the original file before acting.

Why not set a wide threshold and catch every edited copy?

Because false matches grow fast with the radius, and on a list of known illegal imagery a false match means a removal, a disabled account and possibly a report about an innocent person. So the tight radius acts and the wide radius only routes to review, and the false-match rate is measured per list and per distance band from reviewer outcomes. The wide band is where the copy-detection embedding earns its cost, as a second opinion before a person looks.

Someone crafts an innocent image that collides with a listed hash.

That has been shown publicly: researchers produced collisions for Apple’s NeuralHash within weeks of its release in 2021, and any perceptual hash can be attacked that way. A crafted collision does not look like the listed image, so a hash match is evidence, not proof: account-level actions on a list match need a second signal (the image model, the embedding) or a specialist. A flood of collisions also shows up as one list entry matching thousands of unrelated uploads, which pages the list owner.

A better hash algorithm comes out. Now what?

Then stored hashes do not help and the re-scan has to read media. Compute the new hash for every upload from now on, backfill media viewed in the last 30 days first (a few percent of storage, nearly all of the views), and work through the long tail over months within a fixed budget. Shared lists move to a new algorithm slowly, so both are matched side by side for a long time.

How do you catch spam text disguised with look-alike characters and spacing?

Normalise before anything scores it: Unicode NFKC, look-alike characters mapped to one form (the Unicode confusables data exists for this), zero-width characters removed, repeated letters collapsed, links resolved to their final domain. Then near-duplicate detection on the normalised text, MinHash or SimHash, clusters a spam wave that varies a few words per copy, so one decision covers it. Text drawn into images goes through OCR into the same path.

One upload matches a list of known child sexual abuse material. Do you disable the account at once?

For a tight, high-quality match, policy treats it as severe enough to disable, and the design supports that. Weaker evidence (the wide radius, a low-quality hash, a single match on an account with a long clean history) goes to a specialist first. For our own lists of previously removed media, never automatically: those are reposts of content that violated in its original context, and a strike needs a person or a confident model on this copy.

Scores are not decisions: the policy engine

A new law in one country requires a category of content to be removed there within 24 hours. Do you retrain the model?

There are dozens of policies (spam, adult nudity, sexualised content, graphic violence, hate, harassment, violent extremism, self-harm, regulated goods), each with exceptions (news, art, health awareness, condemnation), applied on surfaces with different standards (a profile photo, a comment, a recommended video) in ~200 countries with their own laws, to viewers of different ages. Policy teams change thresholds or definitions weekly; models retrain monthly at best. If each model emits actions, every policy change is a model change, and nobody can say afterwards which rule removed a given post.

Models are good at one thing: estimating how likely an item is to match a label. Whether that likelihood is enough to remove, limit or review, on this surface and in this country, is policy, and it changes on a different clock and is owned by different people. Keeping the two apart, with the policy versioned, is what lets a statement of reasons cite the exact rule, and what lets a policy change be tried on stored scores without running a single model.

  • Calibrated per-label scores in; a versioned, declarative policy evaluated per surface, country and viewer; actions out chosen
  • Models output actions directly rejected
  • Thresholds written into each consuming service rejected
  • A separate model per language or region situational: for languages where the multilingual model measurably underperforms, as another score source into the same engine

The answer: Every model emits calibrated probabilities per label, such as hate.dehumanising 0.91, with its version, and the engine stores each item’s latest scores (~150 B per item for 90 days). The policy is data: rules matching on labels and score bands, surface, country, viewer age, account signals and detection source, each returning an action (remove, withhold, restrict, age-gate, warning screen, limit, review with a severity, or none), a scope (global or a list of countries) and a queue. Rules run in a fixed order: legal orders and hash-list rules, then country-specific legal rules, then global policy with its exceptions, then defaults; within a scope the most restrictive result wins. A version is an immutable snapshot, changes need a second approver, and each change is replayed over the last 7 days of stored scores (items hit per action, reviews created) and shadowed for a day before rolling out surface by surface. For the new law: a rule scoped to that country with action withhold-in-country and a 24-hour review deadline, plus a replay over stored scores of items currently being viewed there, so existing content is caught without touching a model.

You ship a new hate model and its scores shift. Do all the thresholds break?

Not if every model is calibrated per label before it ships, so that 0.9 means about 90 % of such items violate, measured on fresh labelled sets in the major languages. The rollout compares decision rates per rule under the old and new model on the same items in shadow, and any rule whose hit rate moves by more than a few percent is reviewed before the switch. Rules refer to labels, never to model versions.

Two policies disagree: one removes nudity, another allows it for health awareness.

Exceptions are rules with explicit precedence: an allow rule for a recognised context (health, art, news) runs before the generic remove rule, and its condition is a context label from a model or a reviewer. When the context model is not confident, the result is review rather than removal, because those are exactly the cases automated removal gets wrong.

Content is legal in one country and illegal in another.

Scope is part of the action: the rule withholds the item in the countries where it is illegal and leaves it eligible elsewhere, and the reach gate is read with the viewer’s country. The ledger records the scope and the legal basis, and the notice tells the author where it is withheld and why. A global removal can only come from global policy.

An authority sends a removal order with a one-hour deadline.

That is a source, not a model. The order arrives through a legal intake, maps to a rule with an action, a scope and a deadline, and lands in an S0 queue for legal review. The EU regulation on terrorist content online, for example, gives hosting providers one hour from a competent authority’s removal order, so the deadline drives priority and paging. Every order and its outcome go into the ledger, because transparency reports count them separately.

How do you test a policy change?

Three steps: a suite of known items per rule that must produce the expected action, a replay over 7 days of stored scores reporting hits and reviews created by country and surface, and a day of shadow evaluation on live traffic where decisions are logged but not applied. Reviews created matters most: a change that adds 2 M cases a day silently breaks every queue’s deadlines.

Review queues: expected harm, routing, one decision per cluster

Your reviewers can make 2.6 M decisions a day and 20 M items want one. Which items do people see, and how do you keep the decisions good?

15,000 reviewers across dozens of languages and every time zone: about 11,000 on shift on a given day, 6 hours each on queue at ~40 decisions an hour, ~2.6 M decisions. Candidates: ~20 M items a day in an uncertain band, 2 M reports and ~250 k appeals. A first-in, first-out queue spends its day on yesterday’s low-reach spam while today’s viral item waits. And a viral item arrives as thousands of copies; reviewing each copy alone would use a whole site’s shift on one video.

The harm an item can still do is P(violation) × severity × the views it will get if left alone, and the last term varies by orders of magnitude: nearly every candidate will get a few dozen views while limited, a few will get millions. So the queue is a ranking by expected harm prevented, re-ranked as view velocity changes, and an item nobody reaches stays limited, which caps the harm it can do.

  • Expected-harm priority with severity deadlines, routing by policy and language, one case per cluster, and quality control built in chosen
  • First in, first out per queue rejected
  • Order by number of reports rejected
  • Automate everything, no people situational: for spam and other high-volume, low-severity policies where models measure above 99 % precision, with sampled human audits

The answer: Each case gets priority = P(violation) × severity weight (S0 1,000, S1 100, S2 10, S3 1) × predicted views over the next 24 hours, from current view velocity, the author’s audience and the cluster’s total, recomputed every few minutes, and a deadline by severity (S0 15 min, S1 1 h, S2 24 h) that takes over as it approaches. Queues are per (policy family, language), reviewers are certified per policy and language, S0 and S1 queues in the main languages are staffed around the clock by sites in different time zones, and rare languages use machine translation for triage with a native speaker for the final call. Cases are per cluster (media by perceptual hash and embedding, text by MinHash on normalised text), and a verdict fans out to every member. The tool shows media blurred and greyscale by default, video as a strip of keyframes with the flagged segments, audio off until asked, and enforces per-shift caps on S0 and S1 items. About 3 % of each queue is known-answer items, about 5 % of decisions get a blind second review, and removing an item with over 1 M views or disabling an account with a large audience needs two reviewers to agree.

Fake views push a borderline item up the queue, or hold another one down.

Predicted reach counts views from accounts with normal histories and discounts bursts from new accounts on one network, the same signals the abuse pipeline uses. Pushing an item up the queue only gets it reviewed sooner, which is harmless. Holding one down needs it to look quiet, and a quiet limited item is not spreading, so the attack mostly delays a review that was not urgent.

Two reviewers disagree on 15 % of hate speech cases. What do you do?

Treat it as a policy problem before a people problem. Disagreement concentrated on one rule or one language means the guideline is ambiguous: policy writes examples for the disputed cases and adds them to the known-answer set. Individual reviewers far below their peers on known-answer items get coaching and leave that queue until recalibrated. Disputed cases go to training with lower weight or a third opinion.

How do you protect the reviewers who see the worst content?

Reduce exposure, then cap it. Hash matches on known illegal imagery are never shown to general reviewers; a specialist team works those queues with tighter limits. Previews are blurred and greyscale until the reviewer chooses to look, video is a strip of frames rather than playback, audio is off, and the tool counts each reviewer’s S0 and S1 items per shift and per week and stops serving them past the cap. Breaks, rotation and counselling are policies; the cap is enforced in code.

A coordinated group mass-reports an activist’s posts. Does volume decide?

No. Reports are weights, not votes: each reporter’s weight comes from how often their past reports were upheld, and reports from recently created or linked accounts count for little. Volume can raise priority, so a person looks sooner, but no number of reports removes content without a model score or a reviewer. Accounts that keep filing rejected reports lose weight, and the DSA expects platforms to act against repeated manifestly unfounded notices.

How do you know review is working?

Time to action by severity against deadlines, views before action for removed items, accuracy on known-answer items, second-review agreement and appeal reversal rate per policy and language. Above all prevalence: the share of a random sample of all views that reviewers label violating. Prevalence is the one number that says whether the whole system works, because it counts what nobody reported and no model flagged.

Reviewer decisions as training labels

Can you train your classifiers on reviewer decisions? Reviewers only ever see what the models and reporters already surfaced.

Reviewer decisions are the most plentiful labels, ~2 M a day, but they are biased three ways. They exist only for items a model or a user surfaced, so whatever the model misses is never labelled. They are made under a policy version, and a definition change can flip last month’s labels. And they are noisy, with disagreement of 5 to 20 % on context-heavy policies. Adversaries also change tactics in days, so a model trained on last quarter’s decisions misses this week’s disguise.

The most useful label is one where the model is unsure or the data is new. A label on an item scored 0.02 or 0.99 teaches the model little; a label at 0.5, in a language the model rarely sees, or on a new cluster moves the boundary. Review capacity is fixed, so a small slice spent on labels chosen for their value, plus a random sample for an unbiased measurement, buys more model quality than the same hours on routine cases.

  • Policy-versioned labels from every decision, weighted by agreement, plus an active-learning slice and a random prevalence sample chosen
  • Train on every reviewer decision as it arrives rejected
  • Expert-labelled sets only situational: for evaluation sets and new policies, where a few thousand careful labels decide whether a model ships
  • User reports as labels rejected

The answer: Every decision in the ledger becomes a label event with its rule and policy version, the reviewer’s certification, whether it was a second review or an appeal, and whether it was later reversed; appeal reversals of automated decisions are the most valuable labels there are and are weighted up. About 5 % of review capacity, ~130 k items a day, goes to items chosen for label value: scores in a band around each threshold, items where two model versions disagree, new clusters, and languages with thin data; they are reviewed like any case and their actions apply. Another ~20 k random views a day, stratified by surface and country, are labelled for prevalence, which gives unbiased recall and the headline number. When a definition changes, the evaluation set is relabelled under the new version first, and training drops older labels for the changed rule until enough new ones exist. Models retrain monthly; a confirmed new evasion goes into hash lists, link blocks or a text rule within hours rather than waiting for the next model.

Isn’t sending low-harm items to review a waste when deadlines are being missed?

The slice is a budget and the first thing to shrink in a surge: S0 and S1 deadlines come first. In normal operation it pays for itself: a model one point more precise at a threshold that removes 100 k items a day avoids about 1,000 wrong removals, and their appeals, every day.

How do you measure recall when you don’t know what you missed?

From the random sample. Label a random sample of views, count the violating ones, and check what share the system had already caught. That is recall measured on views, which weights each miss by its exposure: a viral miss counts once per view, as it should.

One review site applies a policy inconsistently. Do its labels poison the model?

They would, which is why labels carry the reviewer’s site and certification and are weighted by measured accuracy on known-answer items for that queue. A site whose agreement with the known answers drops shows up in the quality numbers within days, its labels for that period are down-weighted, and the cases with the largest disagreement are reviewed again.

Spammers change tactics every few days. Does a monthly model keep up?

Not on its own, and it does not need to. The fast loop is not training: a decision on a new spam cluster blocks its link domains, banks its media hashes and adds normalised text patterns within the hour, while account and network signals catch the accounts behind it. The next model learns the pattern, and meanwhile the near-threshold slice concentrates labels on what is new.

The enforcement ledger: strikes, appeals, reports

A year later a user asks why their account was suspended, a regulator asks how many removals were automated, and an appeal reverses one of the strikes. Where does each answer come from?

Decisions come from many places: hash matches, models under some policy version, reviewers, appeals, authority orders and re-scans, about 45 M a day. Each changes several things: the item’s visibility, its reach, the author’s standing, a notice, sometimes a legal report. If each component keeps its own state (a status column on the post, a strike counter on the account), the notice, the strike count and the transparency report disagree as soon as one write fails or an appeal reverses part of a chain.

The answer is one append-only record of every decision, holding everything needed to explain it, with everything else derived from it. A reversal is a new entry that points to the old one. Strikes, visibility, notices and counts become projections that can be recomputed, and the question of what we knew and which rule we applied always has the same answer.

  • Append-only ledger written first; visibility, reach, standing, notices and reports applied as idempotent projections chosen
  • Status columns on the post and a strike counter on the account rejected
  • Reconstruct decisions from application logs rejected
  • Mutable status plus a separate audit log situational: during a migration from a status-column design, with a reconciler comparing the two until the ledger takes over

The answer: The enforcement service writes each decision to the ledger before applying it: decision id, subject (item, cluster member or account), action and scope, rule and policy version, source (hash list and version, model, reviewer, appeal, authority order, re-scan), model versions and scores, list distance, pseudonymous reviewer id, an evidence pointer, the parent decision for appeals and reversals, and timestamps. It is partitioned by day, indexed by item and account, and hash-chained per partition as the audit-logging guide describes. Effects are projections keyed by decision id, so they are idempotent: visibility, reach, standing, notices with statements of reasons, NCMEC reports, and the statements of reasons the DSA asks platforms to send to the Commission’s database; a reconciler replays the last day to repair anything that did not apply. Standing is computed from upheld decisions, a strike per violation weighted by severity over 90 days, with a ladder from warning to feature limits to suspension, and an S0 violation disabling at once; a reversal removes its strike and standing is recomputed. Transparency counts are queries over the same rows by decision time, so the report and every individual answer agree.

The ledger write succeeds but the removal fails. Is the post still up?

For seconds at most. Every effect is keyed by the decision id and retried until it applies, and the reconciler compares each projection with the ledger and fixes the difference, so the post comes down on the retry and the notice is sent once. The reverse order, remove first and record later, is the one to avoid: a removal with no record cannot be explained, appealed or counted.

Someone appeals a decision made under a policy that has since changed.

The ledger has the version that applied, so the reviewer sees both the rule then and the rule now. If the action was right under the old policy but the new one allows the content, it is restored and the strike removed, because nobody should carry a penalty for something now allowed. If it was wrong under the old policy, it is reversed as an error, which also counts against the model or reviewer that made it.

How do you make sure an appeal goes to someone else?

The appeal case carries every decision id in the chain, and the router excludes every reviewer in it, preferring another site and a more senior certification. For automated decisions, the first person to see the item decides. Reversal rates are tracked per original reviewer and per model version: for a reviewer it is a quality signal, for a model it is a threshold problem.

A user deletes their account. Must the ledger forget them?

It must stop identifying them wherever the law allows. Their content is deleted with the account; ledger rows keep pseudonymous subject ids so counts and other people’s appeals still work, and the mapping from those ids to the person is deleted. Evidence preserved for a legal duty, such as a report to NCMEC, is kept for the period the law sets under restricted access, then crypto-shredded. The privacy guide covers the general pattern.

What goes into a transparency report?

Per policy and period: items actioned by action and by detection source (found by us before any report, or reported), appeals filed, items restored on appeal and restored without one, authority orders and their outcomes, median time to action, and prevalence. All are ledger queries dated by decision time and reproducible later, and the same rows produce each user’s statement of reasons, so the totals and the individual answers cannot disagree.

The theory behind it

  • Trust and safety, moderation and abuse prevention: Designing defences against spam, fake accounts, fraud and harmful content: layered checks at signup, posting and payment, rules plus machine learning, review queues, hashing known-bad media, reputation, rate limits and feedback loops.
  • Audit logs and tamper-evident records: Designing audit trails that answer who did what and when: what to record, append-only storage, immutability and retention, hash chains and Merkle trees for tamper evidence, querying and exporting, privacy in audit data, and the difference between audit logs, application logs and event sourcing.
  • Media uploads and processing pipelines: How photo and video uploads work at scale: direct-to-storage uploads with presigned URLs, resumable and chunked uploads, processing pipelines for thumbnails and transcoding, metadata and status tracking, deduplication, moderation hooks and serving variants through a CDN.

Related

Report an issue

Something wrong with this question? A number that does not add up, a step whose answer key looks off, a typo. It goes straight to the author with the page you are on.

Watch a strong candidate answer thisTurn by turn, with the design drawn as they talkWalk through →Mock interview