How AST-Based Similarity Catches Disguised Code Plagiarism

When a student copies a classmate’s Java method and runs it through a renamer script—swapping every identifier, shuffling local declarations, and inserting useless control flow—the resulting code can look entirely different at the character level. Yet it still produces the same output, passes the same tests, and carries the same algorithmic structure. A code plagiarism checker that relies only on surface text matching will mark it clean. An Abstract Syntax Tree (AST) comparison will flag it instantly.

AST-based similarity detection operates on the parsed structure of a program rather than its raw text. It extracts the hierarchical tree of operations, control flow, and declarations that a compiler or interpreter builds before generating machine code. By comparing these trees between submissions, detectors can find matching structural fingerprints that survive almost every cosmetic disguise a student can throw at them. This article unpacks how the technique works, where it outperforms classic token-based systems like MOSS, and why real-world integrity platforms layer both approaches—alongside web and AI checks—to stay ahead of increasingly resourceful plagiarists.

What token-based code similarity actually detects—and what it misses

Before we can appreciate the AST difference, we have to understand what token-based detectors see. When you submit a file to MOSS (Measure of Software Similarity), the system runs a lexical analyzer over the source. It strips comments, whitespace, and string literals, then breaks the remaining text into a stream of tokens: identifiers, keywords, operators, and punctuation. For this Python snippet:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

The tokenizer might produce something like:

DEF IDENT LPAREN IDENT RPAREN COLON NEWLINE INDENT IF IDENT EQEQ NUMBER COLON NEWLINE RETURN NUMBER NEWLINE DEDENT ELSE COLON NEWLINE INDENT RETURN IDENT STAR IDENT LPAREN IDENT MINUS NUMBER RPAREN NEWLINE DEDENT

MOSS then applies a technique called winnowing—a form of fingerprinting that selects a subset of k-gram hashes from the token stream, typically every n-th hash, to create a compact signature. Those fingerprints are compared across all submissions in a class to find pairs with high overlap. Stanford’s original 2003 paper on winnowing showed it was resistant to limited local edits, and for two decades MOSS has been the de facto standard in CS departments worldwide.

The problem is that winnowing operates on a flat token sequence. If a student renames every variable—turning factorial into magic and n into counter—the token IDs change and the k-gram hashes shift. If they reorder independent statements (moving the base case below a temporary debug print), the token order fragments. Inserting dummy if-blocks or splitting a single function into two helper functions further scatters the fingerprints. MOSS does employ some clever normalization: it ignores local variable names in many languages and uses an inexact matching heuristic. But in practice, CS professors regularly report that a student who invests even 20 minutes in automated refactoring can drop a MOSS similarity score from 95% to under 40% while preserving the entire algorithmic identity of the solution.

“MOSS caught 9 out of 11 copied pairs when students only changed variable names. When they also reordered statements and extracted helper methods, MOSS flagged only 3 of the 11. And those were the ones who didn’t bother running an auto-refactor tool.” — Dr. Elena Torres, undergraduate CS coordinator at a large public R1 university, recounting a controlled experiment with 124 student submissions across two semesters.
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.

That’s the ceiling of token-based detection: it sees a sequence of lexical atoms, not the actual computational meaning. To catch structural plagiarism, you need to parse code the way a compiler does.

How an Abstract Syntax Tree captures a program’s structural fingerprint

An Abstract Syntax Tree is the hierarchical representation a compiler builds after parsing but before analysis and code generation. It discards syntactic sugar like semicolons, indentation, and parentheses (which are implicit in the tree structure) and retains only the meaningful constructs: function definitions, loops, conditionals, arithmetic expressions, assignments, and their nesting relationships.

Take the factorial function again. When Python’s ast module parses it, we get a tree that can be visualized (in simplified form) like this:

FunctionDef: 'factorial', ['n']
  └── If
       ├── Compare: n == 0
       └── body:
            ├── Return: 1
            └── Return: n * factorial(n - 1)

Now imagine that a second student submits this refactored version:

def compute(value):
    result = 1
    if value != 0:
        result = value * compute(value - 1)
    return result

To a token-based system, the two look very different: different function name, different variable names, an extra local variable, reversed condition, and a different control flow structure visually. But when we parse the second version, the resulting AST is—aside from leaf node names—almost structurally identical: a function with one parameter, a conditional that branches on a base case leveraging a recursive call with a decremented parameter. The subtree corresponding to the recursive multiplication has the same shape. A competent AST comparison engine will match these two strongly, because the tree edit distance is tiny—essentially, rename a few identifier nodes and swap the order of the return branches.

Modern AST-based plagiarism checkers apply several sophisticated techniques:

  • Tree edit distance: The minimal number of insert, delete, and rename operations needed to transform one AST into another. A low distance relative to tree size signals potential plagiarism.
  • Anti-unification: Finding the largest common AST template by replacing differing subtrees with placeholder nodes. This captures the “algorithmic skeleton” shared between two submissions even when implementation details differ.
  • Subtree hashing: Every subtree (e.g., the body of a for-loop, an if-condition expression) is hashed into a structural fingerprint. If two submissions share many identical sub-hashes, it’s strong evidence of copying, regardless of what variable names sit at the leaves.
  • Normalization passes: Tools may normalize canonical equivalences before comparing: converting for loops into equivalent while loops, simplifying constant values, and ordering commutative operands (so a + b and b + a hash identically).

Real tools and where they land on the token-to-AST spectrum

JPlag, developed at the Karlsruhe Institute of Technology and widely used in European universities, has long been the standard-bearer for AST-based code plagiarism detection. It parses submissions into ASTs (via language-specific frontends for Java, Python, C++, and others), converts them into token‑like streams that respect block structure, and then applies an optimized greedy string tiling algorithm—effectively a structural diff. Unlike MOSS’s flat winnowing, JPlag preserves the hierarchical containment: a matching block of tokens must appear within the same parent scope, which makes it far harder to fool by hoisting lines out of order.

Dolos, an open-source alternative from Ghent University, also takes a structural approach, using a CST (Concrete Syntax Tree) and tree-based matching with a web-based comparison UI. It’s newer, fast, and gives detailed visual diffs, though its language support is narrower than JPlag’s.

MOSS remains unmatched in raw scale and speed—it can process thousands of submissions in seconds—but its core weakness is that it can only flatten structure into a token stream. For a thorough source code plagiarism checker to catch disguised plagiarism, you need both the baseline token coverage AND a structural AST layer. That’s where Codequiry’s engine sits: it runs submissions through token-level fingerprinting to catch lazy copy-pasters quickly, then applies AST-based deep matching to catch the students who made an effort to hide their tracks. The pipeline also cross-references code against public web repositories (GitHub, Stack Overflow, tutorial sites) and runs a dedicated AI-code detection model separately, so a single match report covers peer copying, web sourcing, and LLM generation in one view.

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

When AST detection still falls short—and why layering matters

Despite its power, AST comparison is not a silver bullet. A determined student who truly reimplements a solution from scratch (with a different algorithmic approach, data structures, and control flow) will produce a genuinely different AST that matches only at the level of conceptual similarity—which is not plagiarism at all. That’s the desired outcome. But there are edge cases where AST matching generates false negatives even when copying occurred:

  • Different recursion patterns: If the original used head recursion and the copier rewrites it as tail recursion with an accumulator, the AST shape can shift significantly. Tree edit distance may not capture the semantic equivalence.
  • Heavy use of language idioms: In Python, rewriting a for-loop as a list comprehension changes the AST node type entirely (For → ListComp). If a student manually translates a block of logic into comprehension form, the structural signature diversifies.
  • Copying only a conceptual fragment: A student might copy a complex regular expression, a tricky edge‑case handling block, or a data‑loading routine rather than the whole algorithm. That fragment may be too small to survive the fingerprint-threshold cut‑off.

These limitations are why robust platforms combine multiple detection layers. Codequiry’s approach stacks four signals: token similarity (catching verbatim or lightly disguised copying), AST structural similarity (catching refactored copies), web‑source matching (catching snippets lifted from Stack Overflow or tutorials), and AI‑generated code probability (a separate model trained on millions of human‑ and LLM‑written samples). When several signals light up for the same submission pair, the confidence score jumps, and the instructor can see exactly which parts triggered which detector—down to the line level in the integrated code plagiarism checker dashboard.

Codequiry dashboard home with recent checks showing peer, web and AI similarity scores
The Codequiry dashboard — recent checks at a glance with peer, web and AI similarity scores.

Practical steps for CS educators designing assignments that resist refactoring-based cheating

No detection tool, however sophisticated, should be the only line of defense. The most effective integrity strategies pair automated analysis with assignment design that makes structural copying harder to disguise. Here are approaches that veteran CS instructors have refined over years:

1. Require artefact submissions beyond final code

Ask students to submit commit history, design journals, or test‑driven development logs. An AST may hide the provenance of a copied algorithm, but a Git log that shows a single massive commit with 300 lines of perfectly formed code arriving 45 minutes before the deadline tells its own story. Codequiry’s similarity reports can be cross‑referenced against these process artefacts, turning a high structural match into an academic misconduct inquiry supported by timestamp data.

2. Make assignments context‑specific

Instead of “implement a binary search tree,” assign “build a day‑trading portfolio evaluator that uses a self‑balancing BST to track trade‑volume outliers.” The extra domain semantics force students to write glue code that rarely appears in online repositories and to make design decisions that create unique AST footprints. Copied code from a generic BST tutorial will then fight syntactically with the real‑world context, making structural inconsistencies easier to spot.

3. Rotate problem variants across semesters

If the same assignment with the same starter code runs for five terms, a hidden market of past solutions accumulates. Token and AST fingerprints from obsolete submissions can still be fed into the detection pool—Codequiry allows you to include archive classes in the peer comparison set—but rotating the core requirements each term breaks the reusability chain.

4. Use in‑class “code snapshot” exercises

A 30‑minute in‑class coding exercise on a novel mini‑problem gives you a baseline AST fingerprint for each student’s genuine coding style. When their take‑home project ASTs deviate wildly from that baseline—different function‑size distributions, different error‑handling patterns, unusual indentation consistency—it’s a signal worth investigating even if the similarity score with peers is moderate.

How web‑sourced code and AI‑generated code interact with AST-based detection

Many instructors assume that web‑copied code will be caught by the same peer‑based AST comparison. But if only one student copies a tutorial snippet, there’s no peer to match against. That’s why a AI code detector and a web‑source index are essential companions. Codequiry ingests billions of public code fragments from GitHub, Stack Overflow, tutorial blogs, and open‑source repositories. When a submission’s AST subtree matches a public snippet—even one that isn’t in the class corpus—the report flags the external source and shows a side‑by‑side diff. Similarly, LLM‑generated code often exhibits predictable structural signatures: unnaturally consistent variable‑naming patterns, over‑commenting with generic docstrings, and a tendency toward certain safe‑idiom choices that human students in a time‑pressured lab rarely produce. Codequiry’s AI detector analyzes these signals independently and can even attribute to specific models (ChatGPT, Copilot, Claude) with reasonable accuracy, giving instructors an additional data point when an AST match is ambiguous.

Take a real scenario from Spring 2024 at a mid‑sized liberal arts college: a data‑structures class had a high similarity cluster on a graph‑traversal assignment. The AST comparison showed three submissions with near‑identical tree structures for the DFS and BFS methods, but the variable names were all different, the looping styles varied, and the comment styles were inconsistent. The token similarity scores sat in the 40–60% range—ambiguous. The web‑match module then revealed that all three had imported the same 14‑line adjacency‑list builder from a 2017 Medium tutorial. The AI detector also flagged the BFS implementation in one of the three as likely ChatGPT‑generated, with a confidence of 91%. Layering those signals gave the professor a clear picture: two students copied the tutorial snippet and wrote the rest themselves; a third used ChatGPT for the traversal logic and then all three happened to share the same tutorial‑derived helper. Without the AST layer catching the structural match and the web module identifying the external source, the tutorial snippet would have gone unnoticed.

What to look for in a modern code integrity platform

The above scenario isn’t unique to that college. Across thousands of institutions, the pattern is the same: students mix peer copying, web sourcing, and AI generation within a single assignment, and single‑signal detectors let too much slip through. When evaluating whether your current checker is sufficient, ask whether it does all of the following:

  • Compares submissions using both token‑level fingerprints and AST structural fingerprints to catch both lazy and determined plagiarists.
  • Checks against a current, frequently updated index of open‑web code, not just a static corpus from 2015.
  • Provides an AI‑generated code detection module that is trained specifically on code (not natural‑language text) and can handle multiple LLM families.
  • Offers a clear, filterable report that an instructor can review in under five minutes—not a pile of raw diff outputs.
  • Supports the languages you actually teach: Java, Python, C, C++, JavaScript, and more, with accurate AST parsers for each.

Codequiry was designed from the ground up to meet those criteria. Unlike MOSS, which only does token similarity and requires email‑based submission via a Stanford script, Codequiry gives a full dashboard, detailed similarity side‑by‑side views, web‑source citations, and an AI‑code detector all in one place. Unlike generic text‑plagiarism tools that happen to accept code, it understands language‑specific syntax and semantics, so it doesn’t flag boilerplate import statements or standard for‑loop headers as plagiarism. And unlike many open‑source tools, it offers a commercial support tier and an API that engineering teams can plug directly into CI pipelines—for contractor code originality verification or internal integrity audits, not just classroom use. For institutions ready to move beyond single‑signal detection, a code plagiarism checker with multi‑layer analysis is the only way to stay ahead of the techniques students actually use today.

Frequently Asked Questions

Can AST‑based plagiarism detection catch code that uses completely different variable names?

Yes. Because ASTs represent structure rather than text, the identifier leaf nodes are typically normalized during comparison. The tree shape remains identical (or nearly so) regardless of what the variables are called. This makes variable‑renaming one of the easiest disguises for AST‑aware tools to see through.

Is MOSS obsolete?

Not at all. MOSS remains the fastest, most scalable free token‑based detector, and it works on a huge number of programming languages with minimal setup. Its weakness is that it handles only surface‑level token similarity. For a comprehensive integrity check, it makes sense to use MOSS as one layer among several—or to use a modern replacement that already bundles token and AST checking together, like Codequiry or JPlag.

Does AST comparison produce false positives for small boilerplate code?

Good detectors apply minimum‑size thresholds and ignore standard library imports and common idioms so that small, naturally similar snippets like public‑static‑void‑main declarations don’t inflate scores. In Codequiry, instructors can also set a similarity threshold per assignment to filter out low‑match noise.

Can AI‑generated code fool AST‑based detection?

Not directly. AI‑generated code, if unique, won’t have a peer submission to match against. That’s why an independent AI detection model is needed—one that evaluates stylistic signatures, perplexity, and other signals to determine whether code was written by a human or an