How Code Similarity Detection Grew From Diff to AI

The first code plagiarism detector shipped with Unix Version 5 in 1974. It was called diff, and it worked by comparing two files line by line. If a student simply copied a classmate’s .c file and turned it in, a quick diff would catch it. But anyone who has marked programming assignments knows that real plagiarism is rarely that lazy. Students rename variables, reorder functions, tweak whitespace—and suddenly the line-by-line diff is useless. The next fifty years of code similarity detection evolution have been a steady arms race between ever savvier obfuscation tricks and increasingly sophisticated analysis techniques.

What follows is the story of how we got from diff to detectors that can flag a submission written entirely by ChatGPT—and why every layer built along the way remains essential today.

The Line Diff Era and Its Breaking Points

For a decade, the tool of choice was some variant of the Unix diff algorithm. It computed the longest common subsequence between two files and produced an edit script that transformed one into the other. When two students submitted files that were 95% identical with only a few lines changed, the evidence was clear. The problem surfaced when students learned to reformat code: they’d swap the order of function definitions, break a long line into several shorter ones, or change all the indentation from tabs to spaces. A line-based diff would report massive divergence even though the code was semantically identical.

# Two Python snippets — line diff sees them as completely different,
# but a human sees the same logic.

# Original
def calc_sum(xs):
    return sum(xs)

# "Refactored" submission
def calculate_total(values):
    total = sum(values)
    return total

Instructors at large universities—places like UC Berkeley and MIT with 600-student CS1 courses—started looking for something that could see past cosmetic changes. By the late 1980s, a handful of custom in-house tools had emerged that stripped comments, normalized whitespace, and compared token streams rather than raw lines. The idea was simple: ignore everything that doesn’t affect program semantics. That kernel turned into the first real generation of plagiarism detectors.

Why Tokenization Changed Everything

Tokenization lifts the comparison above formatting. A tokenizer reads source code and converts it into a flat stream of classified symbols: IDENTIFIER, LITERAL, KEYWORD, OPERATOR, and so on. Two pieces of code that produce the same token stream are structurally identical regardless of whitespace or variable naming. For C and Java especially, this was a breakthrough.

The approach was most famously productized by Alex Aiken’s MOSS (Measure Of Software Similarity) system at Stanford, released in 1994. MOSS tokenized submissions, broke the token stream into overlapping k-grams (usually 5-grams), hashed each gram, and selected a subset of hashes as a “fingerprint.” It then compared fingerprints across all submissions in a class to find document pairs with suspicious overlap. The system scaled to hundreds of submissions, ran entirely server-side, and produced a ranked list of suspicious pairs—all without requiring an instructor to look at a single line of code until the final review.

“MOSS’s real insight was not the tokenization itself, but the way it winnowed the k-grams to get a constant-size fingerprint that still retained high recall. That made it practical for very large classes.”

Even today, a modern source code plagiarism checker still relies on token-based analysis as a first pass. The technique catches straightforward copy-paste jobs instantly, and it’s computationally cheap enough to run across thousands of files in seconds. The limitation—which the next generation attacked—is that token streams are still fragile under structural transformations: swapping loops, flattening recursion, or using different library calls can break the n-gram match.

AST Matching and Structural Fingerprints

During the late 1990s and early 2000s, researchers at the University of Karlsruhe developed JPlag, which introduced a new idea: instead of comparing flat token streams, compile the code and compare the resulting abstract syntax tree (AST). An AST captures the hierarchical, recursive structure of the program—how loops are nested, how expressions are composed, where control flow branches. The algorithm then finds the largest common subtree between two submissions, adjusting for node similarity rather than demanding exact equality.

AST-based detection was a significant leap in refactoring resistance. Consider two Java methods that compute a factorial: one uses a for loop, the other uses a while loop with equivalent logic. Token-based approaches might miss the similarity entirely, but the ASTs of both methods contain matching subtrees for the conditional check and the recursive multiply. JPlag can report a 60–80% match even after the loop construct has been changed.

// Student A: recursive factorial
public int fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}

// Student B: iterative factorial
public int factorial(int num) {
    int result = 1;
    for (int i = 1; i <= num; i++) {
        result = result * i;
    }
    return result;
}

JPlag would flag these two methods as partially similar because the abstract control flow—a base condition plus a repeated multiplication—is shared between the recursion and the loop. The system wasn’t perfect: substantial rearchitecting (e.g., converting a depth-first traversal into a queue-based BFS) could fool it, but it raised the bar for undetectable copying considerably.

One downside of pure AST comparison is that it requires the code to compile. JPlag and similar tools need to invoke a parser and produce a tree, which means they’re language-specific and brittle when submissions contain syntax errors. This tradeoff—deeper structural analysis at the cost of parser fragility—still shapes the landscape today. Many code plagiarism checker platforms address it by layering AST matching on top of token analysis, falling back to token fingerprints when compilation fails.

Fingerprinting Beyond Tokens

While ASTs gave structural awareness, another thread of research tackled the scalability and robustness problem from a different angle: source-level fingerprinting. The 2003 “Winnowing” algorithm by Schleimer, Wilkerson, and Aiken (the same group behind MOSS) formalized the idea of selecting a uniform set of hashes from a sliding window of token n-grams. Winnowing guarantees that any substring longer than a certain threshold will be detected, regardless of where it appears in the document. This made it ideal not just for comparing student submissions to each other, but for matching submissions against a corpus of known code from the web.

The University of California system began experimenting with a fingerprinting approach that pre-indexed millions of lines of open source code, Stack Overflow answers, and tutorial snippets. When a student submitted an assignment, the system would fingerprint the submission, query the index, and report back every matching segment with a source URL. Suddenly the plagiarism net extended far beyond the classroom—to GitHub repos, online tutorials, and coding forum posts.

# A Python Winnowing example (simplified) for detecting
# overlapping hashes across large corpora.

def winnow(text, k=5, window=4):
    hashes = [hash(text[i:i+k]) for i in range(len(text) - k + 1)]
    fingerprints = set()
    for i in range(len(hashes) - window + 1):
        fingerprints.add(min(hashes[i:i+window]))
    return fingerprints

Web-aware fingerprinting forced a rethink in how instructors designed assignments. Simple problem statements (“write a function to reverse a string”) would almost guarantee that a significant fraction of submissions would match publicly available solutions. The tools didn’t just catch dishonest behavior—they exposed how much boilerplate and trivial code students were leaning on. That, in turn, encouraged a move toward more open-ended, project-based assignments that were harder to find online.

The Stack Overflow and GitHub Problem

By 2015, code plagiarism in universities had a new dominant pattern: the copy-paste from Stack Overflow, GitHub gists, or Medium tutorials. A 2017 study at the University of Maryland found that in an introductory Python course with 350 students, 22% of submissions contained at least one recognizably copied snippet from publicly indexed sources. The snippets were often small—two to ten lines—but they were unmistakable because the comments, variable names, and even the specific error-handling pattern matched exactly.

Generic plagiarism checkers built for text essays couldn’t handle this. They treated source code as prose, comparing strings and phrases without understanding that for (int i = 0; i < n; i++) is structurally identical to for (int j = 0; j < count; j++). Instructors needed a detect code plagiarism system that could scan the web for matching source code, compare it to student work at the structural level, and present the matches in a way that allowed human judgment. Web integration wasn’t a nice-to-have anymore; it was table stakes.

The best tools in this era—including the platforms that would evolve into Codequiry—began maintaining their own indexed databases of public code sets. They’d run regular crawls of GitHub public repos, Stack Overflow dumps, and well-known tutorial sites. When a student submittal matched a GitHub source file at 85% similarity with identical AST structure for the core logic blocks, the report didn’t just flag it—it gave the instructor a side-by-side view with the exact URL.

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.

Code Similarity Metrics Become Quantitative

One underappreciated shift that occurred between 2010 and 2020 was the move from binary “suspicious / not suspicious” flags to fine-grained similarity scores expressed as percentages. Tools began reporting not just the top matching pairs, but the full distribution: which files clustered together, how much overlap was attributable to shared boilerplate, and what percentile each submission fell into. Instructors started tuning thresholds—flagging anything above 40% for manual review, auto-clearing anything below 15%, and setting up color-coded dashboards that let them triage a class of 200 in under an hour.

This quantification also surfaced uncomfortable truths. In a typical CS2 course, the median pair-of-submissions similarity for unrelated code is around 12–18% due to shared starter code and language constructs. A similarity spike to 35% wasn’t definitive proof of plagiarism, but it was a strong signal—especially when the same pair of students showed elevated similarity across multiple assignments. The data let instructors look at patterns across the entire semester, not just in isolation.

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 shift also meant that similarity detection had to be fast and deterministic. The same submission run through the checker multiple times had to yield the same percentage. This drove adoption of reproducible fingerprinting and eliminated randomized algorithms that could give different results on re-run, which had been a point of frustration with some older research prototypes.

AI-Generated Code and the Next Detection Layer

When GitHub Copilot launched in 2021 and ChatGPT followed in late 2022, the nature of the problem changed overnight. Students were no longer just copying from each other or from static web sources. They were generating entire functions—sometimes entire assignments—from large language models. The generated code was syntactically clean, reasonably idiomatic, and varied each time the prompt was re-issued. It didn’t match any known web snippet, because it had never existed before. Token-based and AST-based detectors that relied on comparing against a corpus had nothing to compare to; the generated code was novel but non-original.

The solution that emerged over 2023 and 2024 was an entirely new class of detector: the AI-generated code detector. These tools don’t look for similarity to known sources. Instead, they analyze the statistical properties of the code itself—perplexity, token entropy, burstiness, and pattern regularity. LLM-generated code tends to exhibit low perplexity (the model chooses the most probable next token rather than the “creative” one), uniformly distributed token probabilities, and predictable structural patterns that mirror the most common training examples. Human-written code, especially from students still mastering the language, is messier: variable names vary in style, comments appear sporadically, control flow occasionally takes a slightly suboptimal but humanly intuitive path.

// Human-written: uneven use of i, j, comments, slightly redundant logic
int sum = 0;
for (int i = 0; i < data.length; i++) {
    // check bounds
    if (data[i] > 0) 
        sum += data[i];
}

// LLM-generated pattern: crisp, consistent, no extraneous commentary
int total = Arrays.stream(data)
                  .filter(x -> x > 0)
                  .sum();

A hot research topic now is stacking signals. A single metric—say, perplexity below a threshold—produces too many false positives when used alone. But when low perplexity is combined with a web-matching check that returns zero public matches, and the student’s submission history shows a sudden jump in coding style and idiom compared to earlier assignments, the evidence becomes much stronger. This multi-signal approach is the direction that platforms like Codequiry have taken with their AI code detector, adding an AI-origin probability layer on top of the existing web and peer-comparison layers. The result: a unified report that says, “This submission is 92% similar to no known source, but its token distribution is consistent with GPT-4 output at 88% confidence.”

Codequiry AI code detection report with average and highest AI probability and a risk distribution
AI-code detection — probability scores per file, flagging submissions likely written by ChatGPT, Copilot, Claude or Gemini.

Where We Are Now and What Remains Hard

The arc from diff to AI detection has brought us to a point where most honest mistakes and lazy copy-paste jobs are caught instantly, many intentional obfuscations are detectable through structural analysis, and purely LLM-generated work leaves a statistical signature that can be flagged. But the boundary between “acceptable assistance” and “plagiarism” has never been blurrier. A student who uses Copilot to autocomplete a loop might be indistinguishable from one who wrote the loop manually and then asked ChatGPT to refactor it for style. The tools can tell you that something looks machine-generated, but not whether that violates your course’s policy.

What the best performing departments are doing now is not just deploying detectors, but using the data they provide to have better conversations. When a student’s work triggers three different flags—a 65% peer similarity match, a Stack Overflow snippet match, and an AI-origin probability of 70%—the instructor doesn’t just apply a penalty. They sit down with the student and walk through the code, asking them to explain the design decisions. That conversation reveals more than any detector ever could.

Still, the technical work isn’t done. Cross-language plagiarism—translating a Java solution into Python and turning it in—remains a hard unsolved problem, because the ASTs and even the control-flow abstractions look different after translation. Adversarial AI prompting (“make this sound like a confused sophomore wrote it”) is emerging as a countermeasure. And the sheer volume of submissions in MOOCs with 50,000 students requires detection to happen in near-real-time, which places new demands on scalability.

Frequently Asked Questions

What replaced MOSS for code plagiarism detection?

MOSS is still widely used because it’s free and effective for simple token-level matching, but many universities and enterprises have migrated to platforms like Codequiry that add AST-based similarity, web-source checks, AI-generated code detection, and a modern dashboard—none of which MOSS provides. The shift is less about replacing MOSS and more about layering additional analysis on top of its core ideas.

How do AI detectors know code was generated by ChatGPT or Copilot?

AI code detectors analyze statistical patterns in the token stream, measuring metrics like perplexity (how surprised a language model is by the next token) and burstiness (how unevenly complex or simple the code is). LLM outputs tend to have low perplexity and uniform pattern regularity, while human code shows more variation and occasional unpredictability. The most reliable detectors combine this with checks against known web sources and peer work to avoid false positives.

Can code similarity checkers catch heavily refactored or obfuscated code?

Yes, if they use AST comparison or structural fingerprinting rather than relying only on tokens. Renaming variables, reordering independent statements, or changing loop style won’t alter the essential program structure, which an AST-based detector can still match. Extremely aggressive restructuring—like converting an entire recursive algorithm into an iterative one with different data structures—can reduce similarity, but the resulting code often shows enough conceptual overlap to trigger a lower-confidence flag that warrants manual review.

Code similarity detection has come a long way from diff to multi-layered analysis that combines tokens, trees, fingerprints, and statistical AI signals. If you’re ready to see that stack in action on your own assignments or codebase, try the code plagiarism checker that brings all these techniques together in one platform.