Skip to main content
The cover image for "Why I like and recommend using TypeScript"

Why I like and recommend using TypeScript

2 likes

Sidebar

I've recently gotten into using TypeScript when working with web technology, for creating web apps and servers. I find that TypeScript makes JavaScript more enjoyable to use. In this article, I'll explain my reasons why.

I’ve recently gotten into using TypeScript when working with web technology, for creating web apps and servers. I find that TypeScript makes JavaScript more enjoyable to use. In this article, I’ll explain my reasons why.

Improves the developer experience #

The number one benefit for me is that it massively improves the developer experience without impacting the agility of an interpreted language. TypeScript combined with a supported IDE like VSCode(ium) delivers a great experience.

Makes the IDE more helpful #

TypeScript provides type information that helps the IDE help you. Whilst type information is supported for JavaScript, it’s not as reliable or fully featured.

With type information, the IDE can show relevant suggestions for methods and members (ie: IntelliSense), allowing you to see what is inside an object. This increases productivity. TypeScript makes refactoring operations like renaming considerably more reliable as it can accurately find all the uses of a symbol.

VSCode listing the members of an interface
VSCode listing the members of an interface

Types as documentation #

Defining types acts as a form of documentation. This doesn’t replace actual documentation but instead supplements it - it tells you the types that an API expects.

Here’s the signature of a function without any comments. Just the argument names and the types are enough to get an idea of how to use it.

findPath(from: Vector2, to: Vector2, maxDistance?: number): Vector2[];

An alternative to TypeScript is JSDoc. This allows you to document the types in JavaScript using comments instead of type annotations. This is a good stopgap if you have an existing code base in JavaScript, but is quite verbose and not as effective.

/**
 * @param {Vector2} from
 * @param {Vector2} to
 * @param {Vector2} [maxDistance]
 * @returns {Vector2[]} the path
 */
findPath(from, to, maxDistance);

Static linting: errors for types and mistakes #

A secondary benefit for me is that TypeScript will emit compile errors when types do not match. This is very useful for discovering a lot of different problems.

One example of a situation where this helps is when dealing with optional types. TypeScript will tell you if you try to access an optional type without first checking for null/undefined. I find that in a lot of languages, nullability causes a lot of issues. It’s a big issue in Java, for example, and one of the reasons why I prefer Kotlin. Remember the types-as-documentation argument as well: having nullability in the types tells you, as the developer, whether you need to handle the null case.

Type checking is also very useful when refactoring code and updating dependencies, as it gives you compile errors for invocations that don’t match changes made.

If you’re doing development properly, you should have unit tests and code reviews. These have a greater impact on code quality and correctness than TypeScript does, but it doesn’t negate the benefit completely.

Compile-time is better than run-time #

A common complaint II’ve read about TypeScript is that it only does compile-time type checking and not runtime. It’s important to note that compile-time checks are better than run-time checks. It’s good to catch as many errors as possible at compile-time, as run-time is slower and less reliable. Compile-time errors are fast enough to be shown in your IDE as you edit the code.

C and C++ are considered to be strongly typed languages and rely almost entirely on compile-time type checking. The “static” in static type checking means that the types are verified at compile-time. In C, the concept of types doesn’t even exist at runtime. C++ has Run-Time Type Information (RTTI), which allows you to do safe casts at run-time (std::dynamic_cast) and also get type information (typeid and std::type_info). These features are useful but not essential - it’s common for developers to disable RTTI to reduce program size in embedded applications. Now, perhaps C/C++ isn’t the best role model when it comes to type safety. Rust would be a better example, as it has a well-written compiler and is memory-safe.

So, what’s the problem here with TypeScript? The problem isn’t that it only does compile-time checks, the problem is that incorrect types can enter the program. First, incorrect types can enter the program through third-party dependencies. This is especially common when the dependencies are written in JavaScript. Second, TypeScript has dangerous operations that can result in incorrect types. For example, type assertions (as T) allow you to tell the compiler that a symbol is a certain type even if this isn’t true. The use of any is quite dangerous as well.

It’s possible for compile-time checking to be fully capable if you limit the use of dangerous operations. You can use eslint to enforce this in a project.

Dealing with untyped data #

Incorrect types can enter a TypeScript program when interacting with untyped dependencies and data. One way to ensure this doesn’t happen is by using a library like Zod. Zod is a TypeScript validation library that allows you to check unknown data using a schema. It also generates TypeScript types, allowing schemas to work seamlessly with TypeScript.

const User = z.object({
	username: z.string(),
	full_name: z.string(),
	age: z.number(),
});

const user = User.parse(JSON.parse(json));
// `user` will have the correct TypeScript type.

Another cool thing is that Zod can transform data for you whilst validating. If an API gives you a date timestamp in ISO format, you can use z.date() to convert that string to a JS Date object. You can also provide custom transform functions to validate and manipulate the data. This makes it a very powerful tool for dealing with untyped data.

Drawbacks #

Like any technology, TypeScript does have some drawbacks. The main one for me is that it adds complexity to a project. You need to set up a build system to process TypeScript into JavaScript. I don’t think this is a problem once a project reaches a certain size, but if I’m just writing a few small scripts for a website I’ll use plain JavaScript.

A lot of people say they have issues with TypeScript types, but I’m not too worried about this. The majority of my code is fairly straightforward type wise. I have had more issues with Webpack and NPM modules than TypeScript itself.

For another opinion, you might be interested in reading The TypeScript Tax.

Conclusion #

I’m not saying that TypeScript is a perfect language - there’s no such thing, only different tradeoffs. Whether or not TypeScript is worth it depends on your project and priorities.

In my opinion, TypeScript is great when you’re working with web technology and would otherwise be using JavaScript. It improves the developer experience and increases resilience without a huge amount of cost. I find it enjoyable to use and will continue using it for the foreseeable future.

rubenwardy's profile picture, the letter R

Hi, I'm Andrew Ward. I'm a software developer, an open source maintainer, and a graduate from the University of Bristol. I’m a core developer for Luanti, an open source voxel game engine.

Comments

Leave comment

Shown publicly next to your comment. Leave blank to show as "Anonymous".
Optional, to notify you if rubenwardy replies. Not shown publicly.
Max 1800 characters. You may use plain text, HTML, or Markdown.