<h2>Helping people clean large photo libraries without uploading their photos</h2><p>Yeet or Keep is a mobile app that finds screenshots, blurry images, large files, and likely duplicates, then gives the user a fast review flow for deciding what to keep.</p><p>I designed and built the product around a simple constraint: photo content and photo analysis stay on the device. The app can help, rank, and explain, but it should not quietly upload a library or make irreversible deletion decisions for the user.</p><p></p><div data-image-grid="" data-columns="3" class="image-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"><img src="https://www.andrewjf.com/projects/case-studies/yok-01-product.webp" alt="Yeet or Keep cleanup feed showing duplicate groups, downloads, large files, and recoverable storage." style="width: 100%; height: 250px; border-radius: 0.75rem; object-fit: cover;"></div><p><em>Yeet or Keep surfaces useful cleanup groups while leaving every deletion decision with the user.</em></p><h3>At a glance</h3><ul><li><p><strong>Role:</strong> Independent Product Engineer</p></li><li><p><strong>Timeline:</strong> 2026 to present</p></li><li><p><strong>Status:</strong> Active development</p></li><li><p><strong>Focus:</strong> React Native product design, native Android performance, on-device analysis, resumable jobs, and local data</p></li></ul><h2>The product problem</h2><p>Photo libraries are both large and personal. A useful cleanup app has to inspect thousands of files, surface results quickly, remain responsive while the user scrolls or swipes, and recover when the operating system interrupts background work.</p><p>Duplicate detection adds another constraint: a similar-looking image is not necessarily safe to delete. The product needs to distinguish exact copies from likely visual matches and make that uncertainty understandable.</p><p></p><div data-image-grid="" data-columns="3" class="image-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"><img src="https://www.andrewjf.com/projects/case-studies/yok-02-review.webp" alt="Yeet or Keep review screen for deciding whether to remove or retain a suggested photo." style="width: 100%; height: 250px; border-radius: 0.75rem; object-fit: cover;"></div><p><em>The review flow separates the app's suggestion from the user's final decision.</em></p><h2>Making the first scan feel fast</h2><p>The early pipeline enriched each photo one at a time before useful results appeared. On a 6,800-photo test library, the initial scan took 34 seconds.</p><p>I split scanning into stages. Cheap metadata produces useful categories first. More expensive enrichment and image analysis continue in bounded batches. Native modules process metadata and perceptual hashes without thousands of individual JavaScript-to-Android calls. That reduced the measured initial scan to roughly 5 to 8 seconds on the same test library.</p><p></p><img src="https://www.andrewjf.com/projects/case-studies/yok-scan-pipeline.svg" alt="Staged on-device scan pipeline with resumable SQLite checkpoints." style="border-radius: 0.75rem;"><p><em>The app surfaces quick wins first, then spends deeper analysis only on likely candidates.</em></p><h2>Designing duplicate detection for trust</h2><p>The app does not treat one similarity score as permission to delete a photo. It first uses metadata to find likely candidates, then applies native perceptual hashing to compare visual content. Exact file hashing is reserved for candidates that need byte-for-byte proof.</p><p>This staged approach avoids hashing an entire library before the app becomes useful. It also keeps exact duplicates separate from photos that merely look similar, so the review screen can explain the difference.</p><h2>Surviving interruption</h2><p>Long-running work is stored as resumable jobs in SQLite. Foreground and background processing claim the same work, checkpoint progress, and skip completed stages. Heavy analysis pauses or yields while the user is actively browsing, swiping, or viewing media.</p><p>This matters on mobile because the operating system can pause the app at any time. A fast scan that repeatedly starts over is not fast in practice.</p><h2>Results</h2><ul><li><p>Reduced the initial scan of a 6,800-photo test library from 34 seconds to about 5 to 8 seconds.</p></li><li><p>Built a candidate-first duplicate pipeline using metadata, native perceptual hashing, and selective exact hashing.</p></li><li><p>Made scan work resumable across app interruptions instead of restarting the library.</p></li><li><p>Kept photo content and analysis on-device while preserving human review for deletion decisions.</p></li></ul><h2>The tradeoff</h2><p>Yeet or Keep could appear more magical by automatically deleting anything above a similarity threshold. I chose a slower final step: show the evidence and let the user confirm. That adds friction, but the cost of one wrong deletion is higher than the benefit of saving one tap.</p><h2>What I learned</h2><p>Performance on mobile is not one benchmark. It is how quickly the first useful result appears, whether interaction stays smooth while work continues, and whether progress survives the next interruption.</p>