Skip to content

The Drift Log · Software inventory you can trust

Finding Duplicate Helper Functions Across Monorepos

25 October 2026 · 3 min read · 413 words · established

A search beam fanning across a constellation of crystal nodes with one lit bright

Text search misses copy-pasted helper functions. Normalising ASTs into canonical structural hashes reveals duplicate logic across package boundaries.

In a forty-package TypeScript monorepo, there is almost never one exponential backoff helper. There are usually eight.

One lives in packages/core/src/net.ts. Another is copy-pasted into apps/worker/src/utils/retry.ts because importing core dragged in an unwanted database client. Three more live inside third-party API wrappers where an engineer wrote async function withRetry from memory during an on-call incident.

Everyone agrees the team needs an internal library. Most teams even believe they have one. Yet duplicate code proliferates because workspace search stops helping the moment an engineer changes maxRetries to attempts or swaps a for loop for a while statement.

Why Workspace Search Fails to Stop Duplication

Text-based search is sensitive to everything that does not matter and blind to everything that does.

Consider two implementations of a basic retry loop written by different engineers in different packages:

// packages/billing/src/http.ts
export async function fetchWithRetry(fn: () => Promise<Response>, max = 3): Promise<Response> {
  for (let i = 0; i < max; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === max - 1) throw err;
      await new Promise(r => setTimeout(r, Math.pow(2, i) * 100));
    }
  }
  throw new Error("Unreachable");
}
// apps/sync/src/retry.ts
async function executeBackoff<T>(task: () => Promise<T>, attempts = 3): Promise<T> {
  let count = 0;
  while (count < attempts) {
    try {
      return await task();
    } catch (e) {
      count++;
      if (count >= attempts) throw e;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, count - 1) * 100));
    }
  }
  throw new Error("Retry failed");
}

A grep for fetchWithRetry will never find executeBackoff. A search for Math.pow(2 returns dozens of unrelated math operations across the monorepo, burying the signal.

A code reuse audit built on raw text or token matching fails because code identity is structural, not lexical. As long as workspace boundaries create friction around imports, engineers will write local helpers that do the same work with different variable names and different loop constructs.

Normalising Syntax into Structural Hashes

To find duplicate helper functions across boundaries, you must strip away cosmetic variance before comparing implementations.

This is done by parsing the files into an Abstract Syntax Tree (AST) and generating a canonical structural hash for each function:

  1. Strip Identifiers: Replace all local variable names, parameter names, and internal bindings with positional indices (De Bruijn indices). Both max and attempts become $param_1.
  2. Discard Trivia: Strip comments, formatting, and purely syntactic whitespace. Type annotations are normalised into generic slot signatures.
  3. Canonicalise Control Flow: Map equivalent control structures into a single normalised representation, reducing standard counter loops into an identical iteration primitive.
  4. Compute Subtree Hashes: Hash the normalised AST nodes from the leaves up.

When you hash the normalised trees of fetchWithRetry and executeBackoff, cosmetic variations collapse into an identical intermediate representation:

[fn:2] (
  $p0: () => Promise<$T>,
  $p1: number = 3
) -> Promise<$T> {
  [loop:bounded] ($v0: 0 .. $p1) {
    [try] {
      return [await] $p0()
    } [catch] ($v1) {
      [if] ($v0 == ($p1 - 1)) [throw] $v1
      [await] [sleep] (100 * (2 ** $v0))
    }
  }
  [unreachable]
}

Both implementations yield the exact same hash:

sha256:d8b2e1f409ac635882b45e994967399a5c6d321593c23078a6d68b637f90e541

Once functions are indexed by their normalised AST structure rather than their names, discovery becomes an exact match over hash sets. You no longer ask whether an engineer wrote fetchWithRetry or executeBackoff. You evaluate the repository for structural collisions, isolate the duplicate implementations, and replace them with a single tested artifact.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.