Five structural patterns appear in nearly every TypeScript OSS repo I've analysed

After running Hotspots against 28 TypeScript open-source repositories, five structural patterns appear in 86–100% of them. Here's what they are, why they cluster in TypeScript, and what they tell you about where your refactoring budget should go.

Stephen Collins ·
editorial typescript code-health refactoring data

Key Points

What are the most common structural patterns in TypeScript open-source projects?

Across 28 TypeScript repositories analysed with Hotspots, the five most common structural patterns are: god_function (present in 100% of repos), long_function (100%), exit_heavy (96%), complex_branching (93%), and deeply_nested (86%). These are not niche findings — they appear consistently across repos of very different sizes, domains, and maturity levels.

What is a god_function in TypeScript?

A god_function is a function that has accumulated so many responsibilities — and calls so many other functions — that it becomes the de facto owner of a large slice of system behaviour. High fan-out is the primary signal. In TypeScript OSS, god functions tend to appear in control layers: form control factories, schema parsers, query builders, and event dispatchers. They are hard to test, hard to reason about, and expensive to change safely.

Why do these patterns appear in otherwise well-maintained repos?

These patterns accumulate gradually through growth, not negligence. A function starts focused, then handles one more case, then another, until it is managing responsibilities it was never designed for. TypeScript's type system can accelerate this: exhaustive union dispatch is encouraged by the compiler, so functions that handle large type surfaces grow complex by design. The pattern isn't a sign of bad engineering — it's a sign that refactoring hasn't kept pace with growth.

Which TypeScript repos show the most severe structural patterns?

Four repos hit the maximum count (5) on all five structural patterns simultaneously: FlowiseAI/Flowise, chatboxai/chatbox, upstash/context7, and paperclipai/paperclip — all AI-tooling applications. react-hook-form's createFormControl (CC 174) and zod's convertBaseSchema (CC 114) are the most extreme individual function examples. Two repos show notably low pattern diversity: type-challenges (only stale_complex, god_function, and long_function) and ant-design/ant-design-pro (three patterns), both repos with deliberately structured complexity.

What should I prioritise if I find these patterns in my TypeScript codebase?

Start with god_function: a function with high fan-out is the highest blast-radius target — any change can ripple to many call sites. Add characterisation tests first, then extract coherent sub-responsibilities into named functions. complex_branching (common in TypeScript due to discriminated union dispatch) is best addressed with a strategy/dispatch-map pattern rather than simple extract-method. deeply_nested code is often a signal that error handling and guard clauses have accumulated — flatten them with early returns or Result types.

After running Hotspots against 28 TypeScript open-source repositories — from utility libraries like zod and marked to full applications like chatbox and Flowise — five structural patterns appear in nearly all of them. They aren’t concentrated in a particular domain or project size. They show up in actively maintained, widely used projects. They are a consistent feature of TypeScript OSS code at scale.

This post documents what those patterns are, how prevalent they are, and what they suggest about where refactoring effort has the most leverage. Two of the findings are explored in depth in companion posts: the branching vs exit_heavy flip between TypeScript and other languages, and why hub functions are concentrated in UI framework repos.

The Five Patterns

PatternRepos (of 28)Avg countCount ≥ 5
god_function28 / 28 (100%)4.512 / 28
long_function28 / 28 (100%)4.611 / 28
exit_heavy27 / 28 (96%)4.511 / 28
complex_branching26 / 28 (93%)4.013 / 28
deeply_nested24 / 28 (86%)3.68 / 28

Pattern counts reflect occurrences across each repo’s top-risk function band — not a hard cap. A count of 5 or more means the pattern appeared in at least 5 of the highest-risk functions in that repo; some repos show counts of 7 or 8 for their most prevalent patterns.

The two repos missing exit_heavy are outliers for structural reasons: type-challenges/type-challenges is a collection of type-level puzzles where runtime logic is minimal, and ant-design/ant-design-pro is a scaffolding template where the highest-complexity functions are configuration-driven. Both show only three patterns total.

What Each Pattern Looks Like in Practice

god_function — a function that calls so many others, or handles so many concerns, that it becomes the implicit owner of a large system surface. In react-hook-form, createFormControl calls 112 distinct functions and has CC 174. In zod, convertBaseSchema calls 79. The problem with god functions isn’t their size — it’s their blast radius. Every change carries unpredictable side effects.

long_function — a function whose body has grown to the point where it cannot be read in one sitting and its full set of responsibilities cannot be held in working memory. Highly correlated with god_function in this corpus: 11 repos reach count ≥ 5 on both simultaneously.

exit_heavy — a function with many return points distributed throughout its body. In TypeScript, this typically arises from layered guard clauses and validation chains that have accumulated over time. It makes it difficult to track what state is established at any point in the function, and hard to know which return path was taken without a debugger.

complex_branching — a function with high cyclomatic complexity, typically from if/else chains or switch statements. In TypeScript, this pattern is structurally encouraged by discriminated union dispatch — a function that handles every variant of A | B | C must branch on each. See the companion post on branching vs exit_heavy for the full analysis.

deeply_nested — control flow stacked more than four or five levels deep, making any individual branch’s preconditions difficult to reason about without unwinding the full context stack. Often a symptom of deferred flattening: the same function has grown by appending new blocks inside existing ones rather than extracting them.

The Fully Saturated Repos

Four repos maxed out on all five patterns simultaneously — every one of their top-5 risk functions carries all five structural patterns at maximum count:

  • FlowiseAI/Flowise
  • chatboxai/chatbox
  • upstash/context7
  • paperclipai/paperclip

All four are AI-tooling applications. This may reflect a specific growth dynamic: AI-tooling code tends to orchestrate many external calls (LLM APIs, vector stores, streaming pipelines), accumulate many runtime states (loading, streaming, error, cancelled), and evolve rapidly under competitive pressure — a combination that drives functions to accumulate responsibilities faster than they are decomposed. With only four examples, this is a hypothesis rather than a finding; I’ll revisit it as the corpus grows.

How These Patterns Accumulate

The consistent presence of these patterns across repos with very different teams, domains, and sizes suggests they are a product of how software grows, not of individual engineering choices.

A function starts small. Over time it handles one more input type, one more error case, one more configuration option. The TypeScript compiler encourages exhaustiveness at each step — add a new union variant and the compiler surfaces every switch that needs updating. Each update is correct. Each update adds a branch. After enough iterations, the function is a god function with complex branching, long body, nested conditions, and many exits.

This is not a failure of discipline. It is the natural result of a codebase evolving without systematic refactoring. The patterns are signals of where that evolution has outrun the decomposition.

Where to Start

If you find all five of these patterns in your codebase’s highest-risk functions, the priority order is:

  1. god_function first. High fan-out means high blast radius. Add characterisation tests before touching anything, then extract coherent sub-responsibilities into named functions with narrow interfaces.

  2. complex_branching second. For TypeScript specifically, the most effective technique is a dispatch map over a strategy pattern — replace a large switch body with a map of named handlers, each independently testable. Covered in detail here.

  3. deeply_nested alongside complex_branching. Nesting and branching often co-occur. Flattening nesting by extracting inner blocks frequently reduces branching simultaneously.

  4. exit_heavy and long_function last. These are symptoms of the same growth process, and they typically resolve as a side effect of addressing the first three.


This analysis covers 28 TypeScript repositories analysed between March and April 2026. Pattern counts reflect the top-5 hotspot functions per repo as scored by Hotspots’ activity-weighted risk metric. The full list of analysed repos is available in the analysis index.

Hotspots highlights structural and activity risk — not “bad code.” Editorial policy →