Text Deduplication Algorithms — From O(n²) to O(n)
Removing duplicate lines sounds trivial. The naive approach works for small inputs but breaks down at scale. Here's how to do it efficiently.
The Naive Approach: O(n²)
function dedupNaive(lines) { const result = []; for (const line of lines) { if (!result.includes(line)) { result.push(line); } } return result; } Array.includes() is O(n), so this is O(n²) overall. For 10,000 lines, that's 50 million comparisons.
Using a Set: O(n)
function dedupWithSet(lines) { return [...new Set(lines)]; } Set uses a hash table internally, giving O(1) average lookup. This is 100x faster for large inputs.
Case-Insensitive Deduplication
function dedupCaseInsensitive(lines) { const seen = new Set(); return lines.filter(line => { const key = line.toLowerCase(); if (seen.has(key)) return false; seen.add(key); return true; }); } Preserving Original Case While Deduplicating
Keep the first occurrence's original case:
function dedupPreserveCase(lines) { const seen = new Set(); return lines.filter(line => { const key = line.trim().toLowerCase(); if (seen.has(key)) return false; seen.add(key); return true; }); } Sort + Dedup
Sorting first, then deduplicating adjacent duplicates is O(n log n) but useful when you want sorted output:
function sortAndDedup(lines, caseInsensitive = false) { const sorted = [...lines].sort((a, b) => { const ca = caseInsensitive ? a.toLowerCase() : a; const cb = caseInsensitive ? b.toLowerCase() : b; return ca.localeCompare(cb); }); return sorted.filter((line, i) => { if (i === 0) return true; const prev = caseInsensitive ? sorted[i-1].toLowerCase() : sorted[i-1]; const curr = caseInsensitive ? line.toLowerCase() : line; return prev !== curr; }); } Performance Comparison
For 100,000 lines with 50% duplicates:
| Method | Time |
|---|---|
| Naive (includes) | ~2,400ms |
| Set-based | ~12ms |
| Sort + dedup | ~45ms |
The Set approach wins for pure deduplication. Sort + dedup wins when you also need sorted output.
Try it at toolzip.app/tools/text-dedup.