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.