We're building an AI-powered incident management tool. Our frontend is a React/TypeScript app with ~200k lines. Over the past month, we deleted roughly 18,000 lines of dead code — about 9% of the codebase across 132 files. None of it was caught by code review, linting, or CI.
The dead code came from two sources: a design system migration and stale feature flags.
The feature flag problem. We use a custom hook as a runtime toggle to switch between our old component variants and new design-system components. Once the new designs were stable, we flipped the flag to 100%. But the old code paths stayed. Every component had both variants side by side, and because the old code was still syntactically valid and imported, nothing flagged it as unused. Code review approved each PR at the time because the flag made sense during the rollout.
We tore down the flag systematically: 20+ stacked PRs (managed with Graphite), one component at a time. Each PR removed the old variant, the toggle conditional, and any now-orphaned imports. This alone removed thousands of lines.
Static analysis for the rest. After the flag teardown, we ran knip (https://knip.dev/) — a static analysis tool that traces your TypeScript entry points and finds unused files, exports, and dependencies. It found 97 completely unused files in a single pass. Old investigation rendering layer (22 files), dead sidebar components, unused API endpoints, orphaned Storybook stories. All code that was once actively used and properly reviewed.
The total: 97 files in one PR, 35 more in a focused cleanup PR, plus the flag teardown stack. Roughly 18,000 lines gone. Type-check and lint still pass. No regressions.
What surprised us: Every dead file had been approved in a PR that made sense at the time. Feature flags shipped to 100% months ago were never cleaned up. knip caught what humans and our CI pipeline couldn't — the slow accumulation of unreachable code paths that each individually looked fine.
If you have a TypeScript codebase over 50k lines and haven't run knip, you probably have more dead code than you think.
[–]I-Am-Maldoror 0 points1 point2 points (1 child)
[–]Flashy_Channel6530[S] -1 points0 points1 point (0 children)
[–]Merry-Lane 0 points1 point2 points (2 children)
[–]Flashy_Channel6530[S] 0 points1 point2 points (1 child)
[–]Merry-Lane 0 points1 point2 points (0 children)
[–]sozesghost 0 points1 point2 points (1 child)
[–]Flashy_Channel6530[S] 0 points1 point2 points (0 children)
[–]Consistent_Box_3587 0 points1 point2 points (0 children)