How Winnowing Fingerprints Resist Variable Renaming

Winnowing fingerprinting is the sequence‑based locality‑sensitive hashing technique that lets a code plagiarism checker flag two submissions as similar even after a student has renamed every identifier, swapped method order, and peppered the file with dead code. Unlike line‑by‑line diffing or edit‑distance comparison, winnowing operates on token subsequences and is robust to exactly the transformations that first‑year students think will hide copying.

The algorithm was laid out by Schleimer, Wilkerson, and Aiken in a 2003 paper Winnowing: Local Algorithms for Document Fingerprinting, and it powers the engine inside the MOSS (Measure of Software Similarity) system that most CS departments still use. If you’ve taught introductory programming, you’ve relied on it—possibly without seeing how it works under the hood. Understanding the mechanism changes where you set your similarity threshold and what you trust a report to mean.

The token‑based preprocessing that makes fingerprints possible

Before any fingerprint is computed, source code is transformed into a sequence of typed tokens. A scanner walks the file and produces tokens that encode the syntactic category—KEYWORD, IDENTIFIER, OPERATOR, LITERAL, PUNCTUATION—but not the original lexeme. The student’s variable name totalAmount and the original author’s sumX both become IDENTIFIER. The loop body is identical regardless of whether it starts with for or while provided the control‑flow token sequence remains the same.

This abstraction is what makes token‑based fingerprinting resistant to identifier renaming and trivial formatting changes. A concrete Java method like this:

public static int computeTotal(int[] values) {
    int result = 0;
    for (int item : values) {
        result += item;
    }
    return result;
}

tokenizes to something like public static int IDENTIFIER ( int[] IDENTIFIER ) { int IDENTIFIER = LITERAL ; for ( int IDENTIFIER : IDENTIFIER ) { IDENTIFIER += IDENTIFIER ; } return IDENTIFIER ; }. Now rename computeTotal to calcSum, values to arr, and result to tot. The token stream is identical. A line‑based diff would flag a total rewrite; the token stream reveals structural identity.

How winnowing selects fingerprints from the token stream

Naively comparing entire token sequences across a class of 200 students is computationally wasteful. Winnowing reduces the problem by hashing every k‑gram (contiguous token subsequence of length k, typically 5–7 tokens) to a numeric digest using a rolling hash—often Rabin’s fingerprint. From the resulting hash sequence, the algorithm slides a window of size w (commonly 4) and selects the minimum hash value inside each window as a fingerprint. If the minimum value repeats across windows, only the first occurrence is kept.

The beauty of this selection rule is its guarantee: any substring of length at least k + w – 1 tokens will produce at least one matching fingerprint in two documents that share it, provided no hash collisions occur. Practically, with a 64‑bit hash and typical window sizes, collisions are rare enough to ignore. The result is a compact set of fingerprints—roughly 4–8% of the original tokens—that faithfully represent the document’s structural content.

Two submissions are compared by computing the Jaccard similarity or containment of their fingerprint sets. If a student copied the array‑summing function above and then added a bubble‑sort method before it, the fingerprint set of the plagiarized portion will still intersect heavily with the original’s, because winnowing fingerprints are position‑independent. Line‑order shuffling doesn’t hurt the match.

Why variable renaming and comment removal don’t fool it

A common student tactic is to rename variables, alter method signatures, and strip all comments, assuming that “different text” means “different code.” Tokenization erases those surface changes. The winnowing algorithm never sees the names, only the grammatical structure. The same goes for reordering independent functions: each function contributes its own fingerprint cluster, and the overall similarity score reflects the overlap rather than a total sequence alignment.

Comments and whitespace are removed during tokenization, so a submission with every line commented out yields an empty token stream that contributes nothing to the fingerprint set. Students who fill their files with large blocks of commented‑out code are, in effect, removing evidence from their own comparison; the non‑commented core is what drives the similarity, and it rarely hides from the fingerprint set.

Even loop type substitutions (switching a for loop to an equivalent while loop) often produce near‑identical token sequences in languages where the grammar collapses into a small set of control‑flow tokens. A properly designed tokenizer for Java treats for and while as distinct keywords, so a switch does change a few tokens; but the body token stream remains untouched, and the window size absorbs the minor disruption. Similarity percentages typically drop only a few points.

Side-by-side source code comparison in Codequiry showing an 84% match between two submissions
Side-by-side comparison — Codequiry lines up matching code between two submissions and scores the overlap.

The thresholds that make or break a case

MOSS results display a similarity percentage, but that number is not a probability of dishonesty. It is a similarity score that must be interpreted against the assignment size, language boilerplate, and class distribution. For a small assignment—say a two‑class Java program under 100 lines—a similarity of 60% between two submissions often corresponds to near‑identical structural cores with only minor formatting differences. For a larger project with multiple files and shared starter code, a 40% score might be suspicious if the fingerprint matches concentrate in the unique logic sections.

The winnowing algorithm includes a noise‑floor threshold because small fingerprint sets produce unreliable similarity scores. MOSS refuses to flag submissions below about 20–25 tokens unless the similarity is extreme. This means extremely short assignments—a few lines of Python, a single recursive function—can slip through unless the token threshold is lowered or a second detection method is applied.

“I trust a MOSS score above 70% on a small assignment about as much as I trust a compiler error. But between 30% and 50%, I need to read the matched code blocks. Sometimes it’s innocent—a shared library function from a tutorial. Other times it’s a careful refactoring that the fingerprint set barely noticed.” — CS department chair at a mid‑sized R1 university

Different uses demand different thresholds. In a first‑semester Python course with minimal scaffolding, a 55% match is almost certainly copied code. In a senior‑level algorithms class with a common starter kit, 35% might be background noise. Tools that only display a single number without letting you inspect the matched token blocks—and their original variable names—place faculty in an uncomfortable guessing position.

Where winnowing fingerprinting breaks down

Winnowing’s design deliberately sacrifices some sensitivity for speed and robustness. It can miss plagiarism when students alter the control‑flow structure itself: converting a recursive function to an iterative one, unrolling loops, or inlining function calls. The token sequences for those transformations look genuinely different, and the fingerprint overlap craters. A source code plagiarism checker that only uses winnowing will produce a false negative here.

The algorithm is also language‑agnostic but not semantic‑aware. Two students who both implement a binary search tree by following the exact same textbook pseudocode will generate highly similar token streams because the underlying algorithm imposes a specific structure. Winnowing can’t distinguish “plagiarism from another student” from “plagiarism from the textbook,” which is one reason that interpretation always requires a human—and why a checker that also scans public web sources is essential in practice.

Dead‑code injection is harder to defeat than commonly believed. If a student inserts 80 lines of irrelevant arithmetic between the genuinely copied segments, winnowing windows that cross the boundary may pick the dead code’s hash as the minimum, preventing a match. However, if the dead code is syntactically simple (e.g., repetitive assignment noise), the genuine structure still produces fingerprints in windows that fall entirely within the real logic, preserving some similarity. The trick works best when the dead‑code blocks are larger than w tokens and have carefully constructed hash values—something few students have the patience or knowledge to do systematically.

Extending fingerprinting with AST‑based comparison

Modern detection platforms such as Codequiry augment winnowing with abstract syntax tree (AST) hashing, which captures the hierarchical structure of the code rather than its linear token order. While a token fingerprint sees the sequence [if, (, ID, >, LIT, ), {, ID, =, ...], an AST fragment encodes the relationship IfStatement → Condition → BinaryExpression → Plus → …. Two pieces of code that differ in sequential token order but share a subtree—such as a re‑implementation of a method with the variables renamed and the statements inside a loop rearranged—can match at the AST level even if winnowing similarity dips.

AST comparison introduces its own challenges: different parsers produce slightly different tree shapes, and innocuous changes like adding a level of intermediate variable assignment can fragment the tree. However, when used as a secondary signal alongside fingerprinting, it catches the refactoring‑resistant cases that token‑only systems miss. The combination is what makes the difference between catching 92% of real‑world plagiarism instances and sweeping one‑third of them under an acceptable‑similarity rug.

Codequiry web results tracing copied code to GitHub, Stack Overflow and the open web
Web results — Codequiry traces copied code back to GitHub, Stack Overflow and the open web.

What MOSS doesn’t do—and why web checks matter

MOSS compares submissions only against a closed corpus: the set of files you upload. It does not scan Stack Overflow, GitHub repositories, Chegg, or online tutorial sites. The scenario where two students independently copy from the same public source but not from each other is invisible to a peer‑only checker. In large courses, this pattern is far more common than direct peer‑to‑peer copying; a single active GitHub gist for the assignment can spawn a dozen near‑identical submissions that MOSS reports as low similarity because the overloaded TA didn’t manually add the reference solution to the base set.

A detect code plagiarism workflow that includes automated web‑source fingerprinting closes this gap. Codequiry’s engine, for instance, tokenizes the student’s submission and compares its fingerprint set not just against peer assignments but also against a corpus of crawled public code, GitHub repositories, and known solution sites. When a match is found, the report links directly to the source URL, which lets the instructor verify instantly rather than spending 20 minutes reverse‑searching for the snippet that “looks familiar.”

Practical recommendations for tuning fingerprint‑based detection

Faculty and integrity boards often ask what similarity percentage should trigger an action. There is no universal answer, but experience with the winnowing algorithm suggests a layered approach:

  • Above 65%: automatically flag for manual review. In nearly every real‑world data set, scores this high indicate identical structural cores with trivial surface modifications.
  • 40–65%: compare the matched fingerprint blocks visually. Look for concentration in the core logic versus boilerplate. If the only matches are in class declarations and method signatures, it’s likely innocent. If the matches cluster in the algorithmic portions, escalate.
  • Below 30%: generally safe to ignore for larger projects, but suspicious in very short assignments where even a small structural overlap dominates.

The tuning improves when you augment fingerprint‑based comparisons with AST hashing and web‑source checking. Codequiry’s platform presents a combined report that shows winnowing‑based peer matches, AST structural overlaps, and external URL hits in a single view. The instructor can toggle between them and immediately see where one signal is weak but another is strong—for example, a 34% peer similarity that lights up at 82% AST match because a student carefully re‑rolled every loop but preserved the exact algorithmic structure.

Codequiry peer similarity report clustering submissions by risk
The peer view — every submission clustered by similarity, with the highest-risk pairs surfaced first.

Choosing the right tooling means recognizing what specific algorithms see and what they silently ignore. Winnowing is excellent at what it was designed for: fast, scalable detection of token‑level structural similarity that survives cosmetic changes. But a modern integrity workflow demands more. Peer comparison alone misses the Stack Overflow problem. Token fingerprinting alone misses control‑flow rewrites. AST comparison alone can generate false matches on shared toolkit code. The reliable answer is to stack signals—and to trust them enough to investigate, not enough to automate conviction.

Codequiry’s plagiarism checker combines winnowing fingerprinting, AST hashing, web‑source detection, and AI‑generated code scanning in a single dashboard, giving instructors and engineering leads the layered signal they actually need.

Frequently Asked Questions

How does winnowing fingerprinting handle code with mixed languages?

The tokenizer must produce tokens for the specific language being submitted. A Java tokenizer doesn’t parse Python meaningfully, so mixed‑language files often produce sparse, low‑quality fingerprints. For multi‑language projects, the analysis should be run separately per file with a language‑aware pipeline.

Can winnowing detect plagiarism when a student rewrites a recursive function as iterative?

Rarely. The token sequence changes so drastically that fingerprint overlap becomes negligible. This is exactly where AST‑based comparison and web‑source matching provide the complementary signal that winnowing alone cannot offer.

What is the ideal k‑gram and window size for code plagiarism detection?

MOSS uses k = 5 for tokenized grammars and w = 4. These values balance fingerprint compactness with sensitivity. Shorter k‑grams increase false similarity from common boilerplate; longer k‑grams risk missing the match when a few tokens differ. The defaults are well‑tuned for typical codebases.

Does Codequiry use the same algorithm as MOSS?

Codequiry’s peer comparison engine uses fingerprinting that is algorithmically similar to winnowing but extends it with AST‑based structural hashing and a web‑indexed fingerprint database, which MOSS does not provide. This gives a broader detection surface without sacrificing speed.