All Case StudiesSahil Atahar
5 min read #Performance #Typescript

How I Reduced TypeScript Build Time from 1 Hour to 30 Seconds

A TypeScript project that took 1 hour to build was optimized down to 30 seconds by identifying deep recursive type checking caused by large nested interfaces. Splitting form interfaces into smaller, purpose-specific types and removing unnecessary nested fields significantly reduced TypeScript's work during compilation, resulting in much faster builds and a cleaner codebase.

How I Reduced TypeScript Build Time from 1 Hour to 30 Seconds

Overview

When I first started building projects, I was learning TypeScript. I learned TypeScript by building real projects. In my first company, I was working on an HRM and CRM application.

In the beginning, everything was fine. Everything was working properly, but as the project grew, its build time grew too. At first, I ignored it because I was working in a non-IT company where the main priority was implementing features, not frequent deployments.

After a few months, the build became much slower. One day, I had to fix a very small issue and deploy it, and I realized how much time I was wasting. I had never really thought about optimizing the build before.


What Was the Issue?

I started searching the internet for ways to reduce the TypeScript build time. I read many articles, Reddit posts, Medium posts, and more, but I couldn't find the exact solution.

I even asked AI, but it also couldn't identify the exact issue. Then, ChatGPT suddenly suggested that there might be some nested looping issues in my TypeScript interfaces.

That made me look deeper.


How I Found the Problem

After asking more questions, ChatGPT explained that when one interface uses another interface as its type, TypeScript has to recursively check all of its nested types.

That was exactly what was happening in my project.

I had a very large IUser interface, and it was being used inside almost every other interface as createdBy, updatedBy, and deletedBy. Since IUser itself also referenced IUser, it created a deep recursive type check.

interface IUser {
    ...
    createdBy: IUser | string | null;
    updatedBy: IUser | string | null;
    deletedBy: IUser | string | null;
}

What I Did

In my forms, I was already converting these fields into strings using the getIdFromObject() function because createdBy, updatedBy, and deletedBy were properly handled on the server.

However, there were still some fields like assignedTo and reAssignedTo that were handled on the client side.

So, instead of using the complete IUser interface everywhere, I created a separate interface specifically for forms.

interface IUserForm {
    ...
    name: string;
    email: string;
    password: string;
}

First Improvement

Just by doing that, I reduced the build time from 1 hour to around 5–6 minutes.

It was a huge improvement, but it still felt too slow.

Earlier in the project, builds only took about one minute, so I knew there was still room for improvement.


The Main Cause

I researched more and discussed it with AI again.

AI suggested that I should remove unnecessary fields from my form interfaces.

Since I was still new to TypeScript at that time, I didn't realize that I had simply copied the original interface and removed only createdBy, updatedBy, and deletedBy.

There were still many unnecessary fields inside the form interface, such as:

  • Status history
  • Priority history
  • Transfer records

These fields still contained the IUser interface, which meant TypeScript was still doing deep recursive type checking.

So I removed everything that wasn't actually required on the client side.

I updated all the forms to use much smaller interfaces that only contained the fields needed for the UI.

Most of the remaining data was already created and managed on the server.

After doing this, most of my form interfaces became less than half the size of the original interface.

The result?

The build time dropped from 1 hour to just 30 seconds.


How You Can Find Similar Issues

One technique I found for identifying problematic interfaces is by generating a TypeScript trace.

Add this script to your package.json:

"tsc-diagnose": "rimraf trace && tsc -b --diagnostics --generateTrace trace > tsc.log 2>&1"

Run the script.

It will generate a trace folder.

Open the generated trace.json file.

As TypeScript writes the trace, keep scrolling toward the end of the file.

Eventually, you'll notice entries where TypeScript gets stuck with Deep Limit.

Search for Deep Limit.

Go to the first occurrence of it.

Just above that section, you'll usually find the interface or component where the recursive type checking started.

Open that interface and inspect its fields.

In many cases, you'll find a field that references another large interface, which then references the original interface again, creating a recursive type chain.

Reducing or splitting those interfaces can significantly improve build performance.


My Takeaway

TypeScript's type system is incredibly powerful, but very large recursive interfaces can dramatically increase build times.

If you're using the same large interface everywhere, especially in forms where only a few fields are actually needed, consider creating smaller interfaces specifically for those use cases.

This not only improves build performance but also makes your code easier to understand and maintain.


Happy Coding! 🚀

If your TypeScript build is taking much longer than expected, don't just assume that's normal.

Check your interfaces, look for recursive type references, generate a TypeScript trace, and simplify your types where possible.

A few small changes might save you hours of build time.

⚡ Key Results & Impact Metrics

60m → 30sec

Build Time