If you want to know whether a student copied code from Stack Overflow or GitHub, the answer usually lives in the residue: comment text, variable naming, the exact order of imports, the error strings left inside the function. I ran 4,000 bootcamp submissions across three cohorts through web-source matching and 22% of them matched something public. Only about 4% matched 40 lines or more, and those are the ones worth your afternoon.
What follows is what I found, organized by where the code came from, because the source matters more than the percentage.
Why peer comparison alone misses the biggest source pool
MOSS has been the default course tool since Alex Aiken's 1994 paper, and JPlag showed up in 1996. Both do the same job well: compare every submission against every other submission and report pairs that look alike. That catches the two students who split an assignment in the dorm and typed it up separately.
It does not catch the twenty students who each independently copied the same readCsv function off a 2013 Stack Overflow answer. What happens instead is that peer matching flags them in clusters. Student A matches B, B matches C, C matches D, and you end up staring at a connected component of nineteen submissions. Then you sit down with three of them and discover none of them talked to each other. They all read the same answer. The peer report can't tell you that, because the answer isn't in the corpus it searched.
A peer-only check tells you two submissions look alike. It never tells you why.
This is the whole argument for running a web-source pass alongside whatever peer tool you already use. They answer different questions. If you've only ever run Codequiry vs MOSS-style peer comparison, the web check is the half of the picture you've been missing.
What a Stack Overflow match looks like in a real diff
The matched lines are rarely the algorithm. Dijkstra's algorithm is in every textbook and everyone's version converges eventually. What matches is everything around the algorithm.
public static List<String[]> readCsv(String path) throws IOException {
List<String[]> rows = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
while ((line = br.readLine()) != null) {
// handle empty lines and trailing commas
String[] splitted = line.split(",");
rows.add(splitted);
}
}
return rows;
}
That splitted is not a typo a student invents. It's a typo that survives in roughly forty GitHub forks of the original answer, and it's the kind of thing that makes a match trivially verifiable. Same with the comment. Same with the exact sequence of imports and the try-with-resources block wrapping the whole read. Run a token diff against the answer and you get something like 94% on eleven lines.
Here's the part that matters for tool selection. A student who renames splitted to fields, reformats, and drops the comment still gets caught by a tool that compares token streams or syntax trees, because the structure is intact: reader, loop, readline, null check, split, append, return. It does not get caught by a plain string search. That distinction is the difference between a source code plagiarism checker and a text diff tool wearing a costume.

GitHub matches usually arrive with a receipt attached
The GitHub case is my favorite to review because the evidence is so rarely ambiguous. I had a submission last spring that matched a public gist at 91% across 120 lines. The class name was the same, the test method names were the same, and the student had left the original author's MIT license header sitting at the top of the file. Not the license text in a comment somewhere at the bottom. The whole header block, verbatim, including the year 2019 and the original author's handle.
Cross-university copies show up here a lot too. Somebody posts their data structures assignment to a public repo, it gets indexed, and eighteen months later a student in a different country finds it and submits it. The test files are usually the giveaway, because students don't bother writing tests and the original repo had them.
The edge case worth thinking about in advance is self-reuse. A student with four years of industry experience reuses a utility module from their own public repo. That's not plagiarism. That's a policy question about what your assignment is measuring, and it's worth answering in your syllabus rather than in a disciplinary meeting.

Tutorial sites and the line I actually draw
GeeksforGeeks, W3Schools, Real Python, Baeldung. This is the biggest gray zone in the whole category, because the code on those pages is published specifically to be copied. Nobody is being plagiarized in the legal sense. But if the page contains a working solution to the thing you assigned, copying the page is not completing the assignment.
Here's an example that shows up constantly in web dev cohorts:
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
executedFunction is a naming choice specific to one widely-copied tutorial. Finding it in a submission, alongside the same later closure name, tells you the student went to that page. My rule, which I state on day one and put in the syllabus: if the tutorial page solves the assignment, reusing the code without attribution gets you a conversation and a resubmission window. Reusing it with attribution gets you partial credit. Submitting it silently gets you a zero. The rule is about disclosure, not about the code.
Boilerplate, minimum match length, and the bug that flagged 200 students
Every cohort I've taught includes a block of code that fifty students legitimately wrote identically, because I handed it to them. Standard imports, the class skeleton, the if __name__ == "__main__" guard, a logging setup. If your web check doesn't let you exclude the handout, you will spend your weekend reading matches that are your own fault.
I have a cautionary tale. In 2022 I wrote a small shingling script to do web-source matching myself before we had a real tool for it. It worked fine for a semester. Then I normalized punctuation in the tokenizer to catch reformatted code, and that normalization collapsed i++ and i-- into the same token. The next cohort, 200 submissions flagged on a loop counter. I spent a week learning that a detector's aggressiveness is a dial you have to tune, not a setting you leave alone.
What I do now: set the minimum match to around 20 tokens, exclude my handout files explicitly, and ignore any match under 15 lines unless it contains distinctive naming. That combination dropped my false positive rate from uncomfortable to rare.
Read the provenance, not the percentage
A single similarity number is close to useless on its own. An 85% match on a six-line helper function is nothing. A 55% match on a 200-line file is everything. Here's roughly how the sources broke down across my 4,000 submissions, and how I treat each one.
| Source type | Share of matches | Median match length | How I treat it |
|---|---|---|---|
| Stack Overflow | 41% | 9 lines | Warning unless over 30 lines or covers core logic |
| GitHub gist or repo | 24% | 26 lines | Usually a violation |
| Tutorial sites | 19% | 18 lines | Violation if the page solves the assignment |
| Another university's course materials | 9% | 44 lines | Always a violation |
| Pastebin or my own handout | 7% | 5 lines | Ignore |
The column that earns its keep is the last one. A report that just says "87% match" forces you to open every result. A report that says where the match came from and how long it is lets you triage forty results in fifteen minutes.
What goes in the syllabus before the first assignment
Run a check in week two, before anyone has a reason to cheat. Give every student access to their own web similarity results. Make it a teaching moment about attribution instead of a sting operation. I've watched this change behavior more reliably than any warning about consequences, because students genuinely do not know that copying a Stack Overflow answer wholesale is a problem. They think the site exists to supply answers.
Say out loud what counts as a citation in code. A comment with the URL, the author handle if there is one, and the date you accessed it. That's it. If a student with three kids and a full-time job copies eleven lines and cites them, that's a student using a resource correctly, and the code plagiarism checker for teachers that surfaces it should tell you the line count so you know the difference.
Running web, peer, and AI checks in one pass
My workflow is boring and I like it that way. Upload the cohort, get one report with three scores per submission: peer similarity, web similarity, and an AI generation score. Then I work the review queue, which ranks outliers rather than dumping everything on me.
The reason I moved to Codequiry after years of stitching MOSS output into spreadsheets is that it does both halves of the problem. It compares submissions against each other, and it traces them against the open web, GitHub, and online sources, with a token and AST engine that survives renaming and reformatting. It also scores for AI-generated code, which matters more every semester, because a student can paste a prompt, get code back, and leave nothing for a web check to find. When the web score comes back clean and the AI score doesn't, you know which conversation to have.

For the CI side of my life, the REST API means I can run the same checks on a pull request that I run on a student submission. Same engine, different doorway. If you want to see how the reports are structured, Codequiry solutions covers the educator and engineering workflows separately.
Frequently asked questions
Can web code plagiarism detection find code copied from a private repository?
No. Web-source matching only sees what's publicly indexed. A student who copies from a private repo, a paid course, or a private Discord is invisible to a web check. Peer matching catches it only if another student in the cohort had the same access.How long does a copied snippet have to be before it counts?
Set your minimum somewhere around 20 tokens. Below that, you're mostly looking at standard library calls, language syntax, and boilerplate that everyone writes the same way. Above 30 tokens with matching structure and matching naming quirks, the match is deliberate in nearly every case I've reviewed.Does renaming variables or reformatting code hide it from a web match?
It defeats plain text search, which is what most generic plagiarism tools actually do. It does not defeat token-stream or AST comparison, because the control flow, call order, and argument structure stay identical. If you're evaluating a tool, paste in a reformatted copy of a known Stack Overflow answer and see whether it still matches.How is this different from AI code detection?
Web matching traces code back to a specific public source you can point at in a meeting. AI detection scores the probability that a model wrote the code, with no source to cite. You need both, because the two failure modes don't overlap. A pasted ChatGPT answer will never appear in a web index, and a copied gist will never look statistically unusual.If you're running that first-week check this semester, start with the code plagiarism checker and run one cohort through it before you write any policy.