How Codeforces Catches Cheaters: A Look Inside Plagiarism Detection

Revision en1, by saanTH, 2026-07-19 07:13:44

If you've competed on Codeforces long enough, you've probably seen the aftermath of a plagiarism sweep — ratings quietly rolled back days after a round, a wave of "why did I lose rating?" posts, someone's submission flagged with a red "SKIPPED" or a hack. Ever wondered what's actually happening behind the scenes? Here's a breakdown of how automated code-similarity detection works, and why it's harder to dodge than people think.

The core problem: comparing everyone to everyone

A typical Div. 2 round pulls in tens of thousands of submissions per problem. To catch copied code, you'd naively need to compare every submission against every other submission — that's roughly O(n²) pairs. For 30,000 participants, that's on the order of 450 million comparisons per problem. Even at a blistering 10,000 comparisons per second, that's over 12 hours of raw compute for one problem alone.

This is exactly why plagiarism results don't show up immediately after a contest. Ratings get updated first (people are impatient), and the plagiarism pass runs afterward — often taking a few days, especially since close calls get reviewed by a human rather than auto-banned.

The algorithm: winnowing and fingerprinting

The dominant technique here — used by MOSS (Measure of Software Similarity), the Stanford-built tool that's become the industry standard for this kind of detection — is called winnowing. The idea:

Tokenize the code, stripping away things that don't reflect actual logic: variable names, whitespace, comments, and formatting. Generate k-grams — overlapping sequences of consecutive tokens. Hash each k-gram, then select a representative subset of those hashes (the "fingerprint") using a sliding-window rule that guarantees any shared substring of a minimum length is caught by at least one fingerprint. Compare fingerprint sets between submissions instead of raw text. Two files with a high overlap in fingerprints get flagged.

Because the comparison happens on token structure rather than literal text, this approach is deliberately robust to the "cheap" evasion tricks people try first: renaming variables, reordering independent statements, changing whitespace, adding comments, or swapping for loops for equivalent while loops. None of that changes the underlying token sequence enough to break the fingerprint match.

Why it's hard to reliably evade

A few properties make this genuinely difficult to beat, not just annoying to beat:

Noise suppression: matches on tiny common patterns (like a single for loop skeleton every competitive programmer writes) are filtered out, so the system focuses on large, structurally unusual chunks of matching code — the kind that's actually suspicious. Position independence: code doesn't need to appear in the same order in both files to be flagged, so reordering unrelated blocks doesn't help. Human review on close calls: borderline similarity scores aren't auto-punished; a person looks at them, which catches cases where someone tried to be clever about just barely diffing two files enough to dodge a hard threshold. It's checked against templates: standard boilerplate (fast I/O templates, common headers) gets excluded from scoring specifically so it doesn't cause false positives — meaning the system's signal is concentrated exactly on the parts of your code that are actually yours.

There's actual academic literature on attacking MOSS-style systems (one notable paper is literally called "Mossad: Defeating Software Plagiarism Detection"), and it's telling that the successful attacks involve fairly elaborate automated code transformation pipelines — not the kind of thing you improvise during a contest. Cheap surface-level edits, which is what time-pressured contest cheating usually looks like, don't get anywhere near that level of sophistication.

The bigger picture

The interesting takeaway isn't really "here's how to sneak past it" — it's that the whole design assumes cheating attempts are rushed and superficial, and optimizes detection specifically around that. The system isn't perfect (there are Codeforces blog posts from people who've found edge cases and bugs), but its core design — fingerprint on structure, suppress common noise, flag outliers, let humans adjudicate the gray zone — is why "just rename my variables" hasn't worked as an evasion strategy in over a decade of people trying it.

For anyone curious to dig deeper, the original MOSS paper and the Winnowing algorithm paper (Schleimer, Wilkerson, Aiken, 2003) are both worth reading, and Codeforces' own blog has several posts from the community reverse-engineering how their specific implementation behaves in practice.

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en1 English saanTH 2026-07-19 07:13:44 4778 Initial revision (published)