Moving off of TypeScript

Moving off of TypeScript

Chander Ramesh

Sep 17, 2025

We Love You, TypeScript

For nearly five years now, Motion has operated in a large TypeScript monorepo. At its peak, it was roughly ~2.5 million lines of code after excluding comments, node_modules, etc. To manage this, we used Vercel’s rather excellent Turborepo build system.

This is not a blog post hating on TypeScript — quite the opposite! Motion would likely not even have survived until today without it. Motion has pivoted over twenty times. TypeScript let us quickly validate ideas, and most importantly, let all our developers operate in a truly full stack manner. We could quickly and easily share code between the frontend and the backend, we unified our development setup greatly, and of course, we took advantage of the extensive and lively ecosystem.

Our frontend is written in React. Mobile apps? React Native. Desktop app? Electron. Infrastructure as code? Believe it or not, TypeScript. We were (and still are!) huge TypeScript fans.

But over time, things started to change.

The dream of sharing code everywhere never really fully materialized. From the beginning, there were problems sharing code between React Native and the rest of the ecosystem. We had a different version of React and Tailwind from the rest of the web app, so many core libraries were simply never shared. When we did manage to share code, developers would often forget mobile entirely when making changes to shared libraries, resulting in a frequent “who broke the mobile app” hide and seek game. Eventually, we had to abandon React Native for certain screens where performance was critical.

Over time, we dealt with all the typical pain points of a growing TypeScript codebase. The language server frequently crashed, CI builds took forever (our somewhat optimized pipeline regularly still takes ~20+ minutes). A large portion of this will eventually be solved by the move to TS-Go — but this is the exact problem with TypeScript. There’s a lot going on, and I have no doubt that eventually things will be great. But in the meantime, as a rapidly growing and scaling startup, we are not in control of our own destiny.

We’re waiting for the (incredibly talented) TypeScript compiler team to make fundamental changes. We’re waiting for the (really passionate) biome team to support certain features so we can re-enable basic lint rules without nuking our CI times.

We had to wait for Zodv4 to come out just so that our monorepo would even compile because with v3 we were running into max recursion depth issues on the TypeScript compiler! For that matter, why do we need Zod at all? This ceremony is only required because TypeScript doesn’t have runtime types in the first place.

The story doesn’t get any better in the ORM side. As we found out the hard way, Prisma deletes the entire table if your where statement evaluates to undefined. (We ran into many, many other bugs along the way.) Drizzle seems better, and we sponsored them for many months, but it’s not even 1.0 yet. Who’s to say it won’t also have bugs for the first couple years after launch?

The ecosystem is large, vibrant, and passionate. It’s full of people who genuinely want the best for the web and are trying their best. But at the end of the day, as a CTO, I cannot rest the fate of my company in someone else’s hands. The gnawing feeling of helplessness every time we ran into one of these issues was eating away at me — especially when these problems are for the most part solved in many mature ecosystems!

It’s no accident that Java and .NET rule the enterprise world. Runtime types are a given. Relatively fast compile times are mandatory for codebases that are millions of lines long. They both have an ORM that has matured over decades, which means for basic CRUD operations developers simply don’t need to think at all: there is only one real choice. With multi-threading back on the table, we don’t need to spin up complex workflow orchestration solutions to keep a single-threaded event loop free. And ultimately, this means developers can focus on solving novel problems instead of finding increasingly clever solutions to decades-old use cases.

Is it boring? Yes. But solving CRUD operations at scale shouldn’t be exciting. It should be boring and safe. It’s 2025 —we as an industry should know how to scale CRUD apps to hundreds of thousands of queries per second safely and reliably without bugs, without performance bottlenecks, and without “excitement”.

The nail in the coffin came when we developed our agentic workflow system: we needed to have a code block that could execute arbitrary JavaScript. And, for obvious security reasons, that couldn’t be executed in a backend container that also ran JavaScript. This coincided with a major (yet to be publicized) new initiative in the company.

But We Can’t Stay

So if we needed to introduce another language for arbitrary code execution anyway… Could we just start the new initiative in the new language? But that then begged the question of which language.

Two years ago in a now infamous offsite in Chicago, we had a similar fork in the road moment. TypeScript does not and will never have a truly sound type system, and even back then we were having TypeScript fatigue. And while a majority of backend developers wanted to move to a new language, the ensuing flame war between the language choices ultimately led to us remaining on TypeScript. This time, I had learned my lesson: no polls! (Just kidding — in truth, I did discuss it with six or so engineers, but I didn’t want to have a truly open forum.)

The criteria were that I wanted a garbage collected, statically typed language that was performant and maximized productivity. Given that, the only two choices I seriously considered were C Sharp and Java. They both have large, proven ecosystems with extremely mature software.

In the end, I decided on C Sharp, even though I had never used it professionally because I genuinely believe Motion will be far more productive:

  1. Entity Framework

As mentioned above, many of the frustratingly simple problems we dealt with was the lack of a very robust and performant ORM. Most B2B software centers around basic CRUD operations, and we at Motion are no exception. After playing around with it myself, I was absolutely blown away by Entity Framework. At Motion we long struggled with dealing with soft deletes — especially with nested models. But with EF Core’s global query filters, it’s literally a single line of code. There’s many more examples of really advanced use cases (table splitting, concurrency tokens, and navigation properties). But the thing that impressed me the most was how natural the change tracking was. Unlike in Prisma or Drizzle where we had to plumb the transactions through to every method in the repository layer, EFCore operated this way by default. Operations naturally just mutated the context, and saving the context would transactionally commit all changes. At any time, you can simply create a new context (or save the old one).

  1. Similarity to TypeScript

These two languages were created by the same person, so it’s only natural that they look and feel very similar. As many people are starting to realize (heck, Microsoft even advertises this), the language constructs are exceedingly similar. Both have async/await, similar => anonymous function syntax, nullable types, and a lot more. Perhaps the best website comparing and contrasting the two languages is this one by our very own Senior Staff engineer

Charles Chen

! The familiarity was quite important because it meant the velocity hit during this transition would be minimized.

  1. Ecosystem

C Sharp is the fifth most popular programming language (excluding SQL, HTML, and Bash). While it’s less popular than TypeScript or JavaScript, I generally don’t find continued benefits linearly with the size of the ecosystem. Rather, it’s much more similar to logarithmic growth. After a certain point, it plateaus and there isn’t more marginal utility to the extra library, especially when the quality of many of the libraries quite frankly isn’t that high. In that sense, it’s more of a binary test: is the ecosystem large enough — not is this the largest ecosystem. For our use cases, it passed the test with flying colors. C Sharp has a rich history of event sourcing, world class support for the actor model, and really great message buses and more.

  1. AI Coding

C Sharp and .NET play very well with AI coding tools. In general, I’ve found that the stronger the guardrails, the further AI can go without human intervention. When I was playing around with F#, which has a much stricter compiler than Javascript or Python, I consistently found I could send Claude on longer, more complex tasks without oversight and simply trust the compiler to catch most common issues. This Jane Street podcast also briefly touches upon this same phenomenon: the stronger the guard rails, the longer AI can go. I think it’s no surprise that Kotlin and Elixir, both very functional, consistently perform better with code generation (C Sharp is top 5 though!).

Strictness of the compiler aside, C Sharp has hundreds of well established and very performant lint rules thanks to the very well document Roslyn compiler API. One of those lint rules mandates that all public methods and fields have structured XML documentation around them, which is a well-known happy path for AI models. Combined with the sheer volume of well crafted examples from throughout the .NET ecosystem, my very unscientific, vibes-based findings are that AI models do indeed write better C Sharp code than Typescript code given a similar amount of prompting effort.

Conclusion

.NET is not “cool”, and for good reason. For many years it was Windows-only, and it had a reputation of being quite archaic. As Google took Silicon Valley (and the web) by storm, technologies from Mountain View became the de-facto standard.

But .NET Core has been quietly and steadily evolving. This is most definitely not the C Sharp of yore. It’s modern, Linux-first, and open source. It has blazing fast performance and a rock-solid, mature runtime.

I recognize this is a contrarian take. But if you’re the type of developer who has an open mind and prefers some against-the-grain type of thinking, then I think Motion is the right place for you. We’re hiring!

P.S. We’re not the only ones realizing just how productive C# can be!

via Chander Ramesh (Medium)