Duplication explained
Decide which duplicated code to extract and which to leave alone. Learn what fallow dupes reports, how to read clone groups and families, and how clone detection works.
Use this page to decide which duplication in your codebase is worth a refactor. It explains what fallow dupes reports, how to read the output, and when to act.
Clone types
Fallow classifies clones with the standard taxonomy from clone detection research.
| Clone type | What matches | Detection mode |
|---|---|---|
| Type-1 | Exact token sequences (whitespace and comments already stripped) | strict, mild |
| Type-2 | Same structure, but identifiers, literals, or both differ | weak, semantic |
From strict to semantic, recall increases (fallow finds more clones) and precision decreases (you get more possible false positives).
Research: clone taxonomy
The Type-1 through Type-4 clone taxonomy was surveyed by Roy, Cordy, and Koschke (2009). Fallow detects Type-1 and Type-2 clones. Type-3 clones (gapped clones with inserted or deleted statements) and Type-4 clones (semantically equivalent but syntactically different) need more than token comparison. Fallow does not currently support them.
Detection modes in detail
| Mode | What is normalized | Trade-off |
|---|---|---|
strict | Nothing, exact token match | Highest precision, lowest recall |
mild | Same as strict (AST tokenization already strips whitespace and comments) | Default, good starting point |
weak | + string literal values abstracted | Finds copies with different messages or URLs |
semantic | + identifiers + numeric literals + type annotations stripped | Finds clones with renamed variables, most false positives |
● Duplicates (1 clone groups)
196 lines 2 instances dup:6f87acd9
src/lib/dutch-holidays.ts:193-388
src/lib/dutch-holidays.ts:389-584
The two blocks differ only in names such as holidays2024 and holidays2025. Only semantic mode matches them.
Key metrics
Duplication percentage
The share of source lines that overlap at least one clone instance. Fallow computes it over the full analyzed file set, not only the groups that --top shows.
| Range | Interpretation | Action |
|---|---|---|
| 0–5% | Low duplication | No action needed |
| 5–15% | Moderate | Review the largest clone families |
| 15–30% | High | Prioritize extraction of shared modules |
| 30%+ | Very high | Likely a structural issue. Look for copy-pasted modules |
The duplication percentage depends on the mode. semantic mode always reports a higher percentage than strict, because more normalization gives more matches. Compare percentages only between runs with the same mode.
Token count and line count
Each clone group reports both token count and line count.
- Tokens are language-aware units (keywords, identifiers, operators, literals). The detection engine matches on tokens.
- Lines are the source lines that the clone spans. Use lines to estimate the refactoring effort.
A larger clone gives more value when you extract it. A 200-line clone group is worth extracting. A 6-line one probably is not.
Instance count
The number of locations where the same code appears. A clone group with 5 instances means that someone copied the block to 5 places. To fix a bug in that logic, you must update all 5.
| Instances | Risk | Action |
|---|---|---|
| 2 | Normal, often acceptable | Extract if the code is complex or likely to change |
| 3–5 | Bug risk increases | Extract into a shared function or module |
| 5+ | High maintenance burden | Extract urgently. The copies will likely diverge |
Clone groups and families
Clone groups
A clone group is one duplicated code block that fallow found at 2+ locations. Each location is an instance.
● Clone group 1 (8 lines, 2 instances)
src/validators/userValidator.ts:12-19
src/validators/orderValidator.ts:8-15
One block of 8 lines appears in both files:
const errors: string[] = [];
for (const [key, rule] of Object.entries(rules)) {
const value = data[key];
if (rule.required && (value === undefined || value === null)) {
errors.push(`${key} is required`);
}
}
return errors;One clone group is one duplicated block. The same two files can share more than one block:
● Clone group 2 (6 lines, 2 instances)
src/validators/userValidator.ts:24-29
src/validators/orderValidator.ts:20-25
● Clone group 3 (5 lines, 2 instances)
src/validators/userValidator.ts:35-39
src/validators/orderValidator.ts:31-35
These are three separate clone groups between the same two files. This pattern is a clone family.
Clone families
A clone family is a set of clone groups that involve the same files. Those files share several distinct clones, so someone probably copy-pasted one from the other.
Family: src/validators/userValidator.ts ↔ src/validators/orderValidator.ts
3 clone groups, 19 duplicated lines
→ Extract shared validation module
A family needs a different refactor. For a single clone group, you extract a function. For a clone family, you merge or restructure the files.
| Pattern | What it tells you | Refactoring strategy |
|---|---|---|
| Single clone group | One block was copied | Extract that block into a shared function |
| Family within one file | Multiple self-clones | Extract the repeated pattern into a helper |
| Family across 2 files | Files were copy-pasted | Merge into a shared module with configuration |
| Family across a directory | Template-based duplication | Replace the template with a parameterized generator |
When duplication is acceptable
Some duplication is fine to keep:
- Test files: Test cases often repeat setup code on purpose. Shared test setup makes tests harder to read and debug. To exclude test, story, and dev files entirely, use
--production. For more granular control, useduplicates.ignorepatterns. - Generated code: Codegen output (GraphQL, Prisma, OpenAPI) contains duplication by design. Exclude it with
duplicates.ignore. - Small clones (< 10 lines): Very small clones are often idiomatic patterns, such as error handling or guard clauses.
- Intentional copies in one directory:
--skip-localreports only clones that span two or more directories. Use it when you keep side-by-side copies on purpose, such as a.tsand a.jsversion of one file. For copies in different directories, exclude one side withduplicates.ignore.
Premature abstraction is worse than duplication. If the duplicated code does not change and does not cause bugs, leave it. Extract clones that are both large and in actively changing files. To find the files that change often, use fallow health --hotspots.
Interpreting --threshold results
The --threshold flag sets the maximum duplication percentage. If the project is above the threshold, fallow exits with code 1.
✗ 27,255 lines (19.4%) duplicated across 398 files (0.23s)
Duplication (19.4%) exceeds threshold (15.0%)
Start with a threshold above your current level, then lower it over time. For a more gradual adoption, combine it with --baseline.
How suffix-array detection works
Fallow joins all token streams into one sequence and builds a suffix array with an LCP array. Then it scans for repeated subsequences above the minimum length. This runs in O(n log n) time and needs no pairwise file comparison.
Research: suffix-array clone detection
Token-based clone detection was pioneered by Baker (1995). Suffix-array approaches for scalable clone detection were developed by Li et al. (2006). Fallow's approach is closest to the suffix-array method, with normalization levels corresponding to clone types.
Limitations
- Type-3 clones (gapped clones with inserted or deleted lines): fallow does not detect these by default. To find function-scoped near-miss clones with small structural edits, add
--near. Near-miss detection uses semantic normalization and requires at least 80% shingle similarity between every pair in a group. - Type-4 clones (semantically equivalent but syntactically different): fallow does not detect these. Fallow does not flag two functions that do the same thing but are written differently.
- Cross-file-type detection works only between
.tsand.jsfiles (with--cross-language). Fallow does not support other language pairs. - Minimum size thresholds: fallow does not report clones below
--min-tokensor--min-lines. This is intentional, because small clones are usually idiomatic patterns. - Semantic mode false positives: When fallow normalizes identifiers and literals, it can match code that is structurally similar but semantically unrelated. Review semantic-mode results before you act on them.
JSON _meta object
When you pass --explain, or run through MCP, the JSON output includes a _meta object:
{
"schema_version": 10,
"_meta": {
"docs": "https://docs.fallow.tools/cli/dupes",
"metrics": {
"duplication_percentage": {
"name": "Duplication Percentage",
"description": "Percentage of source lines that overlap at least one reported clone instance. Computed over the full analyzed file set.",
"range": "[0, 100]",
"interpretation": "lower is better"
},
"clone_groups": {
"name": "Clone Groups",
"description": "A set of code fragments with identical or near-identical normalized token sequences. Each group has 2+ instances across different locations.",
"interpretation": "each group is a single refactoring opportunity"
},
"clone_families": {
"name": "Clone Families",
"description": "Groups of clone groups that share the same set of files. Indicates systematic duplication patterns (e.g., mirrored directory structures).",
"interpretation": "families suggest extract-module refactoring opportunities"
}
}
}
}
This is an excerpt. _meta has an entry for every metric that the report can emit. It also has a field_definitions block that describes shared output fields such as actions[].
With _meta, AI agents and CI systems can interpret the results without external documentation.