Back to Insights
Developer Tools & DX•Ghosts in the Machine: Why Dead Code Haunts Developers and How Open-Source Alternatives Can Save Them•deep dive•November 1, 2026•14 min read

Ghosts in the Machine: Why Dead Code Haunts Developers and How Open-Source Tools Can Save Them

Dead code is silent technical debt. Learn how it creeps into your codebase, what it really costs, and which open-source tools — knip, depcheck, madge, and more — can banish it for good.

T
Tamiz UddinFull-Stack Engineer

Every codebase has ghosts. They're not the kind that go bump in the night — they're the functions nobody calls, the branches nobody reaches, and the dependencies nobody imports. Dead code is the quietest form of technical debt: it compiles, it lints, it passes tests, and it does absolutely nothing. Left alone, it multiplies.

This article dissects the anatomy of dead code, quantifies the damage it silently inflicts on your build times, bundle size, and team morale, and then walks through a battle-tested arsenal of open-source tools — knip, depcheck, madge, ts-prune, and more — that can exorcise it from your project for good.

1. What Dead Code Actually Is

"Dead code" is a catch-all term, but not all ghosts are the same. Understanding the taxonomy matters because each type requires a different detection strategy.

Unreachable code — statements that can never execute because of an unconditional return, throw, break, or an infinite loop before them.

ts
function explode(): never {
  throw new Error("boom");
  console.log("I can never run"); // unreachable
}

Unused private code — functions, variables, classes, or imports that are declared but never referenced anywhere in the module.

ts
function unusedHelper(): number {
  return 42;
}

export function publicApi(): number {
  return 1;
}

Orphaned public code — exported functions or components that nothing in the codebase imports. This is the sneakiest kind: it looks legitimate, passes type-checking, and ships in your bundle, but no runtime path ever touches it.

Redundant code — logic that has been superseded. A switch branch that can never be reached because an earlier branch handles the same case; an if condition that is always true after a refactor.

Stale feature-flag code — branches guarded by a feature flag that has been permanently enabled or disabled. The flag check remains, but one side of the conditional is now frozen in place.

2. The Real Cost of Dead Code

Dead code doesn't just sit there looking ugly. It compounds across every axis of software delivery.

Bundle size and load time

Bundlers like webpack and Rollup can tree-shake statically analyzable unused exports, but they can't remove everything. Side-effectful modules, dynamic imports, and code accessed via reflection all resist elimination. A single dead module that imports a 200 KB charting library can silently double your initial bundle.

Compile time and CI cost

Every dead file is a file TypeScript must parse, type-check, and emit. Every dead dependency is a package npm install must resolve, and a node module the bundler must walk. In a monorepo with hundreds of packages, a 5% dead-code ratio can add minutes to every CI run — multiplied across every contributor, every day.

Cognitive load and onboarding

Dead code is noise. When a new engineer reads a file, they must mentally execute every branch to determine what matters. A codebase where 15% of functions are never called forces developers to build a mental model of code that doesn't exist. This is the "ghost" in the title: code that feels alive but has no pulse.

Security and supply-chain surface

This is the most underrated cost. A dead import of a vulnerable package still gets installed, still gets scanned by your SCA tool, and still generates a CVE alert. You can't "patch" a dependency you don't use — you have to remove it, which means finding every dead import first. Dead code also hides attack surface: an unused Express route handler is still registered middleware.

Test maintenance

Code that is never called at runtime often has tests written for it anyway. Those tests are also dead. They run on every CI pass, slow down the suite, and give a false sense of coverage. Worse, they lock in behavior you may actually want to delete.

License compliance

Unused dependencies still count toward your license obligations. A dead GPL-licensed package in a proprietary product is a legal liability even if no line of your code imports it.

3. How Ghosts Sneak In

Dead code rarely arrives as a deliberate commit. It creeps in through a handful of well-worn paths.

Refactoring leftovers — you extract a function, delete the original call site, and forget to delete the original body. The most common origin.

Feature-flag removal — you ship a feature, remove the flag from your config, but leave the if (flag) wrapper and its now-dead else-branch behind.

Copy-paste and vendoring — you copy a utility file into your project, use one function, and keep the rest.

Framework scaffolding — create-react-app, next init, and similar generators ship starter components and assets you never touch.

Abandoned experiments — a spike branch merged by accident, a prototype that lost the argument but stayed in the tree.

Gradual API deprecation — you mark a function @deprecated, add a replacement, and plan to remove it in v2. The v2 never ships.

4. The Open-Source Ghostbusting Arsenal

The good news: the open-source ecosystem has matured a complete toolkit for this. Here's the arsenal, organized by detection strategy.

4.1 Compiler and linter flags (per-file static analysis)

The cheapest detection is the compiler itself. TypeScript ships two flags that catch the most common dead code at the module level:

json
// tsconfig.json
{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

noUnusedLocals flags local variables and private functions that are never read. noUnusedParameters flags function parameters never referenced. ESLint's @typescript-eslint/no-unused-vars rule does the same and is more configurable (you can allow _-prefixed args, for example).

These catch local ghosts but miss exported symbols, which is where the bigger problem lives.

4.2 ts-prune — find orphaned exports

ts-prune uses the TypeScript compiler API to walk your entire program and report every exported symbol that is never imported anywhere else.

bash
npx ts-prune

Output looks like this:

bash
src/utils/format.ts:3:2 - formatBytes
src/utils/format.ts:9:2 - formatCurrency
src/components/Button.tsx:1:2 - Button

Each line is an export you can safely delete — if you're not a library publishing that API.

4.3 depcheck — unused and missing dependencies

depcheck analyzes your package.json against your source to find packages you installed but never import, and packages you import but forgot to install.

bash
npx depcheck

It supports a .depcheckrc file for ignoring intentional entries:

json
{
  "ignores": ["@types/*", "eslint", "prettier"],
  "ignorePatterns": ["dist", "build", "coverage"]
}

4.4 knip — the modern Swiss-army knife

knip is the most powerful open-source tool in this space. It combines dependency detection, unused export detection, unused file detection, and workspace awareness in one binary. It understands monorepos, workspaces, and entry-point conventions, and it's fast because it uses the TypeScript compiler API.

bash
npx knip

Example output:

scss
Unused files (2)
src/legacy/old-parser.ts
src/experiments/prototype.ts

Unused dependencies (3)
lodash
moment
axios

Unused devDependencies (1)
@types/node

Unused exports (5)
src/utils/format.ts:3:2 formatBytes
src/utils/format.ts:9:2 formatCurrency
src/components/Button.tsx:1:2 Button
src/api/client.ts:12:2 createClient
src/hooks/useOldHook.ts:2:2 useOldHook

knip is configured via knip.json:

json
{
  "$schema": "https://unpkg.com/knip@5/schema.json",
  "entry": ["src/index.ts", "src/main.tsx"],
  "project": ["src/**/*.ts", "src/**/*.tsx"],
  "ignore": ["**/*.test.ts", "**/*.spec.ts"],
  "ignoreDependencies": ["@types/node", "eslint", "prettier"]
}

4.5 madge — orphaned modules and circular dependencies

madge builds your module dependency graph. It can detect circular dependencies (a common source of subtle bugs) and orphaned modules that nothing imports.

bash
npx madge --circular src/
css
✖ Found 2 circular dependencies!

1) src/index.ts -> src/services/auth.ts -> src/utils/token.ts -> src/index.ts
2) src/components/App.tsx -> src/components/Header.tsx -> src/components/App.tsx

Circular dependencies are a cousin of dead code: they make tree-shaking harder and can cause undefined initialization bugs.

4.6 Coverage tools — find code tests never execute

Coverage tools like c8, nyc, and Vitest's built-in coverage provider identify lines and branches that your test suite never reaches. This is the most behavioral form of dead-code detection — it finds code that is technically reachable but has no tests, which is often a proxy for code nobody cares about.

bash
npx vitest run --coverage

Set a threshold in vitest.config.ts to enforce it in CI:

ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      lines: 90,
      functions: 90,
      branches: 85,
      statements: 90,
    },
  },
});

4.7 Bundler tree-shaking and minification

Your bundler is the last line of defense. Rollup, esbuild, and webpack 5+ can drop unused exports from ES modules, and minifiers like terser and swc can eliminate unreachable branches.

js
// rollup.config.js
export default {
  input: "src/index.js",
  output: { file: "dist/bundle.js", format: "esm" },
  treeshake: {
    moduleSideEffects: false,
    propertyReadSideEffects: false,
  },
};

moduleSideEffects: false tells Rollup that importing a module has no side effects, so it can drop the entire module if none of its exports are used. This is powerful but requires discipline: any module with real side effects (polyfills, CSS imports, global registrations) must be marked as side-effectful or the bundler will incorrectly remove it.

5. A Practical Ghostbusting Workflow

Here's a workflow you can adopt in an afternoon. The goal: catch dead code at every stage — locally, in review, and in CI.

Step 1 — Enable compiler flags. Turn on noUnusedLocals and noUnusedParameters in tsconfig.json. This catches the low-hanging fruit immediately.

Step 2 — Add knip as your primary detector. It covers the widest surface: unused files, exports, dependencies, and workspaces. Add a knip.json config, then run it locally and fix the top-level findings.

Step 3 — Add depcheck as a second opinion. knip and depcheck overlap but each has blind spots. depcheck is better at detecting missing dependencies; knip is better at detecting unused exports and files. Run both.

Step 4 — Run madge --circular. Break the cycles. Circular dependencies are often the reason a module can't be tree-shaken, so fixing them unlocks dead-code removal elsewhere.

Step 5 — Set coverage thresholds. Force every new line of code to be exercised by a test. This prevents new dead code from sneaking in via untested paths.

Step 6 — Wire it into CI. Fail the build on regressions. Add a lint:dead script and a CI step.

json
// package.json
{
  "scripts": {
    "lint:dead": "knip --production && depcheck",
    "test:coverage": "vitest run --coverage",
    "check:cycles": "madge --circular src/"
  }
}
yaml
# .github/workflows/ci.yml
- name: Check for dead code
  run: npm run lint:dead

- name: Check for circular dependencies
  run: npm run check:cycles

6. A Real-World Exorcism, Step by Step

Let's trace a real cleanup on a small TypeScript project. Here's the file tree:

css
src/
  index.ts
  utils/
    format.ts
    legacy.ts
  components/
    Button.tsx
    Modal.tsx
  api/
    client.ts

Run knip:

bash
npx knip

Output:

scss
Unused files (1)
src/experiments/prototype.ts

Unused dependencies (2)
lodash
moment

Unused exports (3)
src/utils/format.ts:3:2 formatBytes
src/utils/format.ts:9:2 formatCurrency
src/components/Modal.tsx:1:2 Modal

Now run depcheck:

bash
npx depcheck

Output:

markdown
Unused dependencies
* lodash
* moment

Missing dependencies
* zod

depcheck reveals two things knip missed: zod is imported somewhere but missing from package.json, and both tools agree lodash and moment are unused.

Run madge --circular:

bash
npx madge --circular src/
bash
✖ Found 1 circular dependency!
src/index.ts -> src/api/client.ts -> src/index.ts

Now the fixes:

  1. Delete src/experiments/prototype.ts — it's a dead file.
  2. npm uninstall lodash moment — dead dependencies.
  3. npm install zod — missing dependency.
  4. Delete formatBytes and formatCurrency from format.ts — orphaned exports.
  5. Delete Modal.tsx — orphaned component.
  6. Break the cycle between index.ts and api/client.ts — usually by extracting the shared type or moving the import.

Re-run all three tools:

bash
npx knip && npx depcheck && npx madge --circular src/

Clean output. Your bundle is smaller, your CI is faster, and your next developer won't have to reverse-engineer a ghost.

7. When Dead Code Is (Sometimes) Fine

Not all dead code should be removed. Know the exceptions:

Public library APIs. If you publish an npm package, your exported functions are your contract. Removing them is a breaking change. Dead code here is a semver problem, not a hygiene problem. Use knip with an entry/ignore config for your public API, or mark the exports as intentional.

Feature flags you plan to reuse. A flag that's toggled off in production but will return is not dead — it's dormant. Keep it, but track it.

Generated code. Build artifacts, generated API clients, and schema types are regenerated. Don't hand-edit them, and configure your tools to ignore them.

Defensive code for impossible states. A branch that handles a theoretically impossible input is a safety net. Removing it can turn a graceful failure into a crash. If you keep it, add a comment explaining why.

Dead code as documentation. Rare, but occasionally a function is kept solely to illustrate an API pattern. This is a judgment call — and it should be a deliberate one, not an accident.

Frequently Asked Questions

Will removing dead code break my app?

No — if the code is genuinely dead. The risk is misidentification: a tool might flag an export that's actually used by a dynamic import(), a test file, or a plugin system. Mitigate this by running your full test suite after each removal, using coverage thresholds to confirm nothing regressed, and reviewing knip's findings before deleting. For libraries, treat exported API as live code regardless of internal usage.

What's the difference between knip and depcheck?

depcheck focuses on package.json dependencies — it tells you which packages you installed but never import, and which you import but never installed. knip is broader: it detects unused files, unused exports, unused types, unused dependencies, and works across monorepos and workspaces. knip is generally the better primary tool; depcheck is a useful second opinion for dependency-specific blind spots.

Should I remove dead code in a library?

Only if you're ready for a major version bump. In a library, an exported symbol is part of the public API even if nothing in your repo uses it. Remove it in the next major release, document the removal in a changelog, and use knip's ignore config to keep your public API out of the dead-code report.


Want more deep dives on developer tooling and codebase hygiene? Explore Tamiz's Insights or the tamiz.pro engineering blog.

Hands-On: Banishing Ghosts from a Real Codebase

Theory is great, but nothing beats the visceral satisfaction of watching dead code get deleted. Let's walk through a practical, end-to-end cleanup using the tools we've discussed. We'll use a realistic, if slightly contrived, TypeScript project to demonstrate the workflow.

The Haunted Project Setup

Imagine a small utility library called string-utils. It's been around for a few years and has accumulated some cruft. Here's the (simplified) file structure:

text
string-utils/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts
│   ├── format.ts
│   ├── legacy/
│   │   ├── old-parser.ts
│   │   └── deprecated-helpers.ts
│   └── utils/
│       ├── is-email.ts
│       └── random.ts
└── tests/
    ├── format.test.ts
    └── is-email.test.ts

The src/index.ts is our public entry point:

typescript
// src/index.ts
export { formatDate, formatCurrency } from './format';
export { isEmail } from './utils/is-email';

We have a legacy folder that hasn't been touched in ages. Let's see what's inside.

typescript
// src/legacy/old-parser.ts
// This was used by a previous version of the app but is now obsolete.
export function parseLegacyString(input: string): string[] {
  // ... 50 lines of convoluted regex logic
  return input.split(',');
}

// src/legacy/deprecated-helpers.ts
export function toUpperCaseSafe(str: string): string {
  return str ? str.toUpperCase() : '';
}

export function toLowerCaseSafe(str: string): string {
  return str ? str.toLowerCase() : '';
}

And our utils folder:

typescript
// src/utils/is-email.ts
export function isEmail(value: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(value);
}

// src/utils/random.ts
// A utility that was used once in a demo and then forgotten.
export function getRandomString(length: number): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

Finally, src/format.ts:

typescript
// src/format.ts
export function formatDate(date: Date): string {
  return date.toISOString().split('T')[0];
}

export function formatCurrency(amount: number, currency: string): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: currency,
  }).format(amount);
}

// This function is used internally but never exported from index.ts.
// It's still "alive" because it's used within this module.
function padToTwoDigits(num: number): string {
  return num.toString().padStart(2, '0');
}

The Hunt Begins: Running knip

Let's run knip on this project. We'll install it first:

bash
npm install -D knip
npx knip

The output will look something like this:

text
Unused files (2)
src/legacy/old-parser.ts
src/legacy/deprecated-helpers.ts

Unused dependencies (0)
Unused devDependencies (0)
Unlisted dependencies (0)

Unused exports (3)
getRandomString (used in 0 files)
toUpperCaseSafe (used in 0 files)
toLowerCaseSafe (used in 0 files)

Excellent! knip has identified the two files in the legacy folder as completely unused, and it's flagged three exports as unused. But wait—it didn't flag padToTwoDigits. Why? Because knip is smart enough to detect that it's used within format.ts (even though it's not exported). It's not dead code; it's just private.

The Vulture's Perspective

Now, let's run vulture on the same project to see if it catches anything different:

bash
pip install vulture
vulture src/ --min-confidence 80

Vulture's output:

text
src/legacy/old-parser.ts:1: unused function 'parseLegacyString' (80% confidence)
src/legacy/deprecated-helpers.ts:1: unused function 'toUpperCaseSafe' (80% confidence)
src/legacy/deprecated-helpers.ts:1: unused function 'toLowerCaseSafe' (80% confidence)
src/utils/random.ts:1: unused function 'getRandomString' (80% confidence)

Vulture agrees on the obvious dead code, but it also flags parseLegacyString, which knip didn't. Why? Because knip treats files as "unused" if they aren't imported anywhere, while vulture analyzes the code within files. Since old-parser.ts is never imported, both tools consider it dead, but knip groups it as an unused file, while vulture looks at the function level.

The Exorcism: Removing the Ghosts

Now that we've identified the culprits, we can confidently delete them. Let's remove the entire legacy folder and the random.ts file:

bash
rm -rf src/legacy
rm src/utils/random.ts

We also need to update src/index.ts if it exported anything from those files (it didn't, but it's good practice to check).

After removal, let's re-run knip to confirm the codebase is clean:

bash
npx knip

Output:

text
Unused files (0)
Unused dependencies (0)
Unused devDependencies (0)
Unlisted dependencies (0)

Unused exports (0)

Clean! The ghosts are gone, and the codebase is leaner. But we're not done yet. Let's think about the why.

Why Did This Happen? A Post-Mortem

The dead code in our example didn't appear overnight. It was a gradual accumulation. Here's the likely timeline:

  1. The Legacy Parser: This was written for a v1 of the application. When v2 introduced a new parsing strategy, the old parser was left in place "just in case." That "just in case" never came.
  2. The Deprecated Helpers: These were utility functions that were used in a few places. Over time, refactoring replaced them with more robust alternatives (like the native toUpperCase method), but the old helpers were never removed.
  3. The Random String: This was added for a demo feature that was later scrapped. The function remained, unused, in the codebase.

This is the classic pattern of dead code accumulation. It's rarely a single act of negligence; it's a thousand small decisions to "leave it for now" that compound over time.

Advanced Patterns: When Tools Need Help

Sometimes, even the best tools need a little human guidance. Let's explore a few scenarios where you might need to configure your dead-code detectors.

Scenario 1: Dynamic Imports

Consider a plugin architecture where modules are loaded dynamically:

typescript
// src/plugins/index.ts
import fs from 'fs';
import path from 'path';

const pluginFiles = fs.readdirSync(__dirname).filter((file) => file.endsWith('.js'));

export function loadPlugins() {
  return pluginFiles.map((file) => {
    // Dynamic import - tools can't statically analyze this!
    return import(path.join(__dirname, file));
  });
}

knip and vulture will flag all your plugin files as unused because they can't trace the dynamic import. You need to configure them.

For knip, you can use the include or ignore options in knip.json:

json
{
  "include": ["src/**/*.ts"],
  "ignore": ["src/plugins/**/*.ts"]
}

Alternatively, you can use the entry configuration to tell knip that the plugin files are entry points:

json
{
  "entry": ["src/index.ts", "src/plugins/**/*.ts"]
}

For vulture, you can use --ignore flags:

bash
vulture src/ --ignore "src/plugins/*"

Scenario 2: Public API Exports

If you're building a library, your public API is the contract with your users. Tools might flag exports as unused if they're not used within the project, but they're part of the public interface. This is where configuration becomes crucial.

For knip, you can define which files are entry points (the public API):

json
{
  "entry": ["src/index.ts"]
}

This tells knip that src/index.ts is the entry point, and all exports from it are considered used. However, if you have a file like src/utils/helpers.ts that exports functions used only in tests, you might want to configure knip to ignore test files:

json
{
  "ignore": ["tests/**/*.ts"]
}

For vulture, you can use the --ignore-decorators or --exclude options, but the most straightforward way is to add a vulture_whitelist.py file:

python
# vulture_whitelist.py
# This file is used to whitelist functions that are part of the public API.
from src.utils.helpers import publicFunction

publicFunction  # noqa

This tells vulture that publicFunction is used (by the whitelist itself), so it won't be flagged.

Scenario 3: Conditional Exports and Environment Variables

Sometimes, code is conditionally exported based on environment variables:

typescript
// src/index.ts
export * from './utils/is-email';

if (process.env.NODE_ENV === 'development') {
  export * from './utils/dev-tools';
}

Static analysis tools will see the export * from './utils/dev-tools' and might not flag it, but if NODE_ENV is never 'development' in production, that code is effectively dead in production. Tools like knip can't easily handle this, so you'd need to either:

  1. Use a build-time constant that the tool can understand.
  2. Manually ignore the file.
  3. Use a tool like eslint-plugin-no-restricted-imports to prevent accidental imports.

The Psychological Toll: Why We Don't Delete

We've covered the technical side, but let's address the human side. Why do developers feel so much resistance to deleting code?

  1. The "What If" Factor: We're conditioned to think that code might be useful someday. This is a cognitive bias called loss aversion—we feel the pain of losing something more than the pleasure of gaining something. But dead code is not an asset; it's a liability.

  2. Fear of Breaking Things: We worry that deleting code will break something we can't see. This is where automated tests become your safety net. If you have good test coverage, you can delete with confidence.

  3. The "It's Not My Code" Problem: If you didn't write it, you might feel uncomfortable deleting it. But code ownership is a myth. Once code is in the repository, it's the team's responsibility.

  4. Lack of Time: We're always behind schedule. Deleting code feels like a luxury, not a necessity. But dead code slows down development—it increases cognitive load, makes code harder to navigate, and can even cause bugs (like when a developer uses a deprecated function thinking it's the current one).

The Business Case for Clean Code

Let's quantify the cost of dead code. A study by Microsoft Research found that developers spend about 50% of their time on "technical debt"—code that isn't adding new value. Dead code is a significant contributor to this.

Consider the following:

  • Cognitive Load: Every time you open a file, you have to understand what it does. If 20% of the code is dead, you're wasting 20% of your reading time.
  • Build Times: Dead code still needs to be compiled or transpiled. Removing it can speed up your build.
  • Bundle Size: For frontend applications, dead code increases bundle size, leading to slower page loads and higher bandwidth costs.
  • Security: Dead code can contain vulnerabilities. Attackers can exploit code that you forgot you had.

In our example, the legacy folder contained regex logic that might have had ReDoS (Regular Expression Denial of Service) vulnerabilities. By deleting it, we've reduced our attack surface.

The Future: AI-Assisted Dead Code Detection

We're on the cusp of a new era. Large Language Models (LLMs) like the one you're reading this on are starting to be used for code analysis. Tools like CodeQL and Semgrep are already using static analysis to find security vulnerabilities, and they're getting better at finding dead code.

Imagine a tool that can:

  1. Analyze your entire codebase and understand the intent of every function.
  2. Identify code that is not just unused, but conceptually dead—code that doesn't contribute to the application's purpose.
  3. Suggest refactorings that consolidate similar functions.

This is still a research area, but the direction is clear. The tools we've discussed today are the foundation; the future will be smarter, faster, and more integrated.

Conclusion: Embrace the Delete

Dead code is not a technical problem; it's a cultural one. We need to shift our mindset from "code is an asset" to "code is a liability." Every line of code is a line that needs to be understood, tested, and maintained.

The tools we've explored—knip, vulture, ts-prune, eslint-plugin-unused-imports, and jscpd—are not just utilities; they are enablers of a healthier codebase. They give us the confidence to delete, to refactor, and to move forward without the ghosts of the past weighing us down.

So, the next time you see a function that hasn't been called in months, or a file that hasn't been imported in years, don't hesitate. Run your dead-code detector, verify the results, and hit delete. Your future self—and your teammates—will thank you.


This deep dive was brought to you by the team at Tamiz's Insights. For more on developer tooling, codebase hygiene, and the art of maintaining large-scale software, be sure to check out our engineering blog.