14: Exploring TypeScript: Runtime

What's the deal with runtime?

8/20/20246 min read

TypeScript 3D logo on it's back - blog title - wip-podcast.com
TypeScript 3D logo on it's back - blog title - wip-podcast.com

This is part of a semi-monthly series that will put TypeScript under a microscope to become more adept overall. šŸ”¬

Understanding the nitty gritty bits and pieces of a language can only benefit us as software builders!

This post will cover runtime.

  1. šŸ¤” What is runtime?

  2. 🦾 Reconstruct and confirm runtime types

  3. šŸ“š Resources for further reading

Let's go!

šŸ¤” What is runtime?

To start, let’s better grasp what type of ā€œruntimeā€ I’m referring to and what it actually means in that context.

Here, runtime refers to the process of a computer interpreting and performing a program’s instructions. Think of each line of code in your program or file as a line of instructions to be carried out!

In the first post of this series, I wrote about the TypeScript Compiler.

What does the compiler have to do with runtime?

We learned that JavaScript code is generated from our TypeScript code through the compilation steps, which is what our runtime will use! JavaScript runtime is often executed in Node but could also be accomplished using Deno, Bun, or a web browser.

It’s interesting to note that TypeScript is statically typed. The types are checked at compile time, not runtime, like JavaScript or other dynamically typed languages. This process checks your code to help find syntax issues or correct misusage in advance.

Why is this helpful?

  • This allows your IDE to offer some powerful tooling and reduces errors upfront!

    • Issues can be caught before a user experiences something your team missed.

  • You’ll achieve better readability and maintainability for your future self and teammates. When written well, TypeScript reads like good documentation.

    • There should also be less cognitive load, scrolling, and searching for files; your IDE will show you relevant information when you mouse over variables!

What does runtime have to do with TypeScript, then?

Simply put, TypeScript types don’t exist at runtime.

Come again?

Yes, that’s right. TypeScript types are ā€œerasable,ā€ removed from the compiled code. Interfaces and type annotations also fall under this umbrella of removed code. Although we haven’t covered declaration files, the types and interfaces described in these files will also disappear.

If you recall, we compile TypeScript code into JavaScript. TypeScript is a superset of JavaScript and adds more functionality on top of JavaScript.

As a result, TypeScript-specific features and functionality disappear and can’t affect your JavaScript code. Therefore, interfaces, types, and type annotations cannot affect runtime behavior.

That’s not at all to say that TypeScript is useless! On the contrary, it empowers your JavaScript code output and developer experience if you take advantage of them, even if aspects disappear when your program hits runtime.

In my opinion, the biggest benefit of using TypeScript types is having a pre-defined ā€œshapeā€ of the types you work with.

This requires thoughtful intention! I’ve worked on projects that aren’t strict with typing and it caused me some headaches. When done well, defined shapes have clarified exactly what I’m working with while building and developing.

As a simple example to play around with type ā€œshape,ā€ here we define the ā€œshapeā€ of an object we want to use to describe my three pets:

Before we run this code, we’ll encounter issues. In VSCode, for example, a little red squiggly identifies issues with the code snippet.

  • We tried to use a string to describe Rayla as ā€˜6 months’ instead of the expected number input for her age (0, 0.5, or 1 depending on your own interpretation). Whoops! What were we thinking?

  • Afterward, we created an imaginary cat. Because it’s not real, we aren’t sure how old it is! Bummer. Alas, Pet as an interface is looking for an age field.

Wait, this example uses an interface, and that goes away after compile time, right?

Correct; I’m so glad you brought that back to the forefront. Let’s look into how you can still ensure type safety at runtime!

🦾 Reconstruct and confirm runtime types

Although TypeScript-specific features don’t exist at runtime, there are ways to ensure that runtime is safe. Don’t fret!

Here’s a great summary pulled from Effective TypeScript that is a quick way to describe some of what we’re about to cover:

If given the time to contemplate, one could probably come up with all sorts of ideas and examples. But here, we’ll cover 3 I use pretty frequently, plus examples.

  1. Validate inputs from external sources

  2. Check types or properties to handle in your code

  3. Use a discriminated (or ā€œtaggedā€) union

Hint: You can use the following commands to follow along with examples in #2 and #3 using TypeScript in Node!

Validate inputs from external sources

You can usually find me building APIs with TypeScript.

This means we receive data from the outside world for most endpoints, which we have no control over, sent to us. Malicious actors may send questionable data to attempt to take advantage of our API!

I recommend validation because we honestly have no idea what a user (whether malicious or misinformed) may try to send us. You could build your own, but I’ve had success using Zod. It has excellent documentation and significant support. I’ve heard Yup is also great, though I haven’t used it personally!

Using validation, we can check that the input is exactly what we need. It can even stop incorrect types in their tracks! At least in my experience with Zod, we can add all sorts of check layers to refine user input before our server fully interacts with it.

Here are a couple of examples:

Another great thing I’ve used with Zod is my own extra layer of refinement to validate the incoming data. Here are two slightly more involved examples:

…And there are all sorts of maneuvers like this you can use to check your data from the outside world to ensure it’s about as safe as you can manage - both for type safety and input security!

Check types or properties to handle in your code

When working with multiple potential types, it’s a good idea to confirm the input's ā€œshapeā€ or data type. This is great both in your TypeScript code and for runtime!

This could be a simple check when you expect, for instance, one type or another. Maybe we know a phone number could be provided as a string or a number, and let’s assume we know the input has the right character count or number length already, but we want the same E164 format output for a North American phone number in either case:

Do you want more straight to your inbox?

Subscribe to receive occassional blog posts!
Your contact information will never be sold.

In this way, although TypeScript types and true type checking won’t exist in the generated JavaScript, we can confirm which types we are working with and how we want to utilize them in a way that will translate into runtime. The generated JavaScipt code looks identical in this case!

We can use a similar concept with objects. Let’s say we’re now talking about storing art pieces in galleries!

Again, the JavaScript code generated appears almost identical! The interface is the main element missing from the compiled code this time, but we can still determine a rough type using property checking in the moveArtworkToGallery function.

The property check only involves values that are available at runtime but still allows the type checker to refine the object's shape to the Art type. That’s great TypeScript code practice and translatable JavaScript all at once!

Use a discriminated (or ā€œtaggedā€) union

This concept is similar to the object case above, and I’ve found it useful to specifically use the field name type, especially for building APIs. As a user I’ve seen it often in third-party APIs, so I feel it’s a common enough practice to lean on.

We are essentially doing property checking again, but in my experience, this is often with a set list of known types. Perhaps this list is described in the API documentation.

Let’s return back to the example of my three pets!

When using a discriminated union (or ā€œtaggedā€ union as it’s described in Effective TypeScript), we are essentially implementing some kind of type storage in our object using a ā€œtagā€ that we can access and use at runtime.

How can we access this? It’s because there is also a value stored in the type field.

I hope that these examples were helpful! Considering how your runtime will read and follow your instructions through your TyeScript code after compilation may help you build better!

šŸ“š Resources for further reading

Thanks so much for reading! ✨

Did I miss anything, or would you like to add anything?

Let me know!

I appreciate constructive feedback so we can all learn together. šŸ™Œ