DEV Community

Cover image for TypeScript Complexity Has Finally Reached the Point of Total Absurdity
Karol Modelski
Karol Modelski

Posted on • Originally published at Medium

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

At some point over the last few years, TypeScript stopped being a tool and started becoming a performance art.

We went from:

  • “Let’s add types so we catch bugs earlier”

to:

  • “Behold my 80-line conditional type that nobody understands, including me.”

The worst part?

This is not a niche problem anymore.

If you work on modern web apps — Angular, React, Payload CMS, Nest, whatever — you have already opened a file where the types look like:

  • a PhD thesis in category theory,
  • accidentally minified,
  • and spread across five utility types imported from different packages.

Somewhere along the way, we quietly accepted that making our code unreadable was an acceptable side effect of ‘taking type safety seriously.’


When Type Safety Crossed the Line

TypeScript was supposed to do two things:

  1. Make JavaScript less surprising.
  2. Make refactors less terrifying.

Instead, in many codebases, it now does a third thing:

  1. Makes every non-author terrified to touch the types at all.

This is especially visible in libraries and frameworks that want to impress other library authors more than they want to be usable by normal humans.

Think about patterns like:

  • deeply nested conditional types with multiple branches,
  • recursive mapped types that try to model an entire CMS schema and all possible query shapes,
  • overly clever generic constraints that explode into 20-line compiler errors if you look at them wrong.

Payload CMS types are a great example: extremely powerful, extremely expressive — and often borderline indecipherable unless you mentally expand half of the type tree. This is not unique to Payload; it is a pattern across many “pro” libraries in 2025.

We wanted guardrails. We ended up building a maze and calling it architecture.


The Hidden Cost of Type Gymnastics

Every time we ship a beautifully complex composite type, we pay a price that does not show up in the type system:

  • Cognitive overhead

    New devs spend days decoding types instead of understanding the domain.

  • Debugging pain

    Compiler errors point into nested conditional types with messages that read like algebra proofs.

  • Refactor paralysis

    Nobody wants to touch core models because one wrong tweak to a generic breaks half the app’s inferred types.

  • Onboarding friction

    Every new hire needs a private masterclass just to understand how types are wired. Until then, they are scared to change anything.

The irony: the more we optimize type cleverness, the less likely it is that anyone will have the courage to keep those types correct over time.

A type system that only one person understands is not a safety net. It’s a single point of failure.


Why We Got Here: Incentives and Ego

How did we end up with types that look like unreadable math equations?

A few predictable reasons:

  • Library ecosystem incentives

    Library authors want strong “DX” stories and love to showcase magical inference that “just works.” Under the hood, that often means very heavy advanced types.

  • Conference / content culture

    Talks and posts about wild TypeScript tricks perform well. Talks about “use simple types, they age better” do not.

  • Engineer ego

    It feels good to bend the type system to your will. It is less glamorous to say, “We could, but we shouldn’t.”

  • Lack of boundaries

    We never collectively agreed on a red line like: “Beyond this level of complexity, we stop and fall back to simpler types and runtime checks.”

So we kept pushing.

And now we have codebases where the runtime logic is straightforward, but the types around it look like an SAT puzzle.

We confuse ‘possible in TypeScript’ with ‘good idea in a real-world codebase.’ They are not the same category.


The Core Argument: Legibility > Exotic Type Wizardry

The case against high-level type gymnastics is simple:

If your types are more complicated than the logic they protect, you are probably doing it wrong.

Strong types are valuable.

But they are not free.

They cost:

  • time to read,
  • time to onboard,
  • time to refactor,
  • and time to debug when the abstractions crack at the edges.

This trade-off used to be obviously worth it: basic interfaces, enums, discriminated unions — huge value, small complexity.

Today, for many teams, the curve has flipped:

  • marginal safety improvements,
  • at the cost of major complexity increases.

At that point, you are not buying reliability.

You are buying a false sense of control while quietly making your system more brittle.

The job is not to push TypeScript to its limits. The job is to make future changes less dangerous for normal humans.


Simple Rules for Saner Types

If you are feeling called out by your own code right now — good.

The way out is not “stop using TypeScript.” The way out is to use it like an adult instead of like a magician.

A few rules that help:

1. Prefer explicit types over over-inferred magic

It is often better to:

  • write an explicit interface or type alias,
  • accept slightly more verbosity,

than to rely on a tower of generics that infers everything but is impossible to understand.

2. Cap the depth of advanced types

Informal rule:

  • one level of conditional types → okay,
  • nested conditionals + recursion + intersected mapped types → probably not okay.

If a type spans more than 10–15 lines and requires comments to explain itself, consider breaking it apart or simplifying the expressiveness.

3. Model the domain, not the entire universe

Types should describe:

  • the shapes you actually use,
  • the constraints the domain really has.

They do not have to model *every possible* configuration that a library theoretically supports.

90% coverage with 30% of the complexity is often a better trade.

4. Use runtime validation when static types fall apart

If advanced types start fighting you:

  • add good runtime validation (e.g., Zod, Yup, custom validators),
  • keep static types modest and understandable,
  • let the runtime layer enforce the last mile of safety.

5. Measure complexity by “who can safely edit this”

If only the original author can modify a type without fear, it is too complex.

Make it a standard that:

  • at least two other devs can explain and edit any core type definition,
  • if not, it must be simplified or documented to death.

A slightly less clever type that everyone understands is worth more than a perfect type that everyone tiptoes around.


Where This Hurts Most: API-heavy and CMS-heavy Apps

The worst offenders for type gymnastics today are often:

  • Strongly typed CMS and headless platforms

    Payload, Contentful, Sanity, custom internal CMSs — all trying to give you perfect type safety for content schemas and queries.

  • API client generators

    Tools that generate types for every endpoint, every param, every response shape.

  • Form and query builders

    Libraries that expose highly generic builder patterns and want types to follow every chain.

These tools genuinely solve real problems.

But without restraint, they create types that are:

  • amazing for autocomplete demos,
  • brutal for maintenance and debugging.

As an application team, you are allowed to say:

  • “We will wrap this with simpler app-level types.”
  • “We are okay with losing some perfect inference in exchange for code we can reason about.”

Enterprise-grade type safety is useless if it also creates enterprise-grade fear of touching the code.


A Healthier TypeScript Culture Going Forward

We do not need to abandon advanced TypeScript.

We need a new default stance:

  • Use simple, explicit types by default.
  • Reach for advanced types when there is a clear, recurring pain they solve.
  • Stop shipping clever type tricks as proof of competence.

What that looks like in practice:

  • Code reviews that ask “Is this readable?” before asking “Is this the most generic possible?”
  • Libraries that publish both “full magic” and “simple mode” APIs.
  • Teams that accept small runtime checks instead of pushing every edge-case into the type system.

If you are a senior developer, this is on you.

Your juniors and your future self will live inside the choices you make about types today.

You are not paid to impress the TypeScript compiler. You are paid to make change safer for your team.


I fix the Angular apps that generalists break.

I’m Karol Modelski, senior Angular developer and frontend architect rescuing legacy B2B SaaS frontends.

If your Angular app is slowing your team down, start with a 3‑minute teardown of your current setup: https://www.karol-modelski.scale-sail.io/

Top comments (0)