Skip to main content

· 20 min read

TypeScript's type system allows any two types that seem to have the same structure to be assignable to each other. But what if we want to restrict a type to only allow certain values, even if other values happen to have the same structure? Say, marking a difference between sanitized and un-sanitized strings, or positive integers from all numbers?

This need is solvable with a pattern called "branded types", sometimes also called "opaque types". Let's dive into why one might want to use branded types, how to declare and use them, and some alternatives to the pattern.

· 20 min read

One long-requested feature for TypeScript is the ability for its types to describe what exceptions a function might throw. "Throw types", as the feature is often called, are used in some programming languages to help ensure developers call functions safely.

The popular strongly typed language Java, for example, implements throw types with a throws keyword. Without reading any other code, a developer would infer from the following first line of a positive function that the function is able to throw a ValueException:

java
public static void positive(int value) throws ValueException { /* ...*/ }
java
public static void positive(int value) throws ValueException { /* ...*/ }

Throw types are also useful for developer tooling. They can tell compilers when a function call might throw an exception without safe try/catch handling.

That all seems useful, so why doesn't TypeScript include throw types?

In short, doing so wouldn't be feasible for TypeScript -- and some would argue isn't practical in most programming languages. This blog post will dig into the benefits, drawbacks, and general blockers to including throw types in TypeScript. Let's dig in!

· 20 min read

TypeScript's provides several ways to describe the type of a function that can be called in multiple different ways. But the two most common strategies -function overloads and generic functions- don't help much with how the function's internal implementation understands the types of its parameters.

This article will walk through three techniques for describing a parameter type that changes based on a previous parameter. The first two allow using standard JavaScript syntax but aren't quite precise in describing the types inside the function. The third one requires using a ... array spread and [...] tuple type in a funky new way that gets the right types internally.

· 20 min read

Most projects in the JavaScript/TypeScript ecosystem release new versions with numbers respecting semantic versioning, or semver for short. Semver is a specification that describes how to predictably increase a package's version numbers upon each new release. TypeScript is notable for not following a strict interpretation of semver for its releases. This article will dig into:

  1. What semver is and why it's useful for many packages
  2. Why following a strict interpretation of semver would be impractical for TypeScript
  3. How TypeScript's releases are versioned to an interpretation of semver that makes sense for it

While TypeScript's diverging from a common community specification can be irksome for developers, there are real reasons why it chose to diverge.

The reasoning can be summarized as:

  • Nuances of TypeScript's type checking change in virtually every release
  • It would be impractical to increase TypeScript's major version for every type checking change
  • If we consider those type checking nuances as details rather than the public API, TypeScript actually has quite good adherance to semantic versioning

This article will also more deeply explain each of those points.

· 20 min read

TypeScript is a highly configurable language. It comes with over a hundred compiler options that can be provided via the command-line to tsc and/or in a "TSConfig" configuration file (by default, tsconfig.json).

tip

TypeScript's compiler options are documented at aka.ms/tsconfig.

compilerOptions.target in particular can be an important configuration option for your project. It specifies which ECMAScript version your project's output JavaScript code must support.

You can specify target in your TSConfig as the string name of an ECMAScript version, such as "es5"or"es2021":

jsonc
// tsconfig.json
{
"compilerOptions": {
"target": "es2021"
}
}
jsonc
// tsconfig.json
{
"compilerOptions": {
"target": "es2021"
}
}

This article explores what target influences and why that's useful. Let's dig in!

· 15 min read

TypeScript 4.9 introduces a new operator, satisfies, that allows opting into a different kind of type inference from the type system's default. satisfies brings the best of type annotations and default type inference together in a useful manner. Let's explore the new satisfies operator and why it's useful!

· 20 min read

TypeScript's type narrowing is a powerful feature of TypeScript's type system that lets it infer more specific types for values in areas of code. For example, TypeScript would understand that inside the following if statement, the fruit variable has to be the literal value "apple":

ts
const fruit = Math.random() > 0.5 ? "apple" : undefined;
 
fruit;
const fruit: "apple" | undefined
 
if (fruit) {
fruit;
const fruit: "apple"
}
ts
const fruit = Math.random() > 0.5 ? "apple" : undefined;
 
fruit;
const fruit: "apple" | undefined
 
if (fruit) {
fruit;
const fruit: "apple"
}

But, TypeScript's type system isn't perfect. There are some cases where TypeScript can't narrow types exactly the way you might want.

Take a look at this code snippet, where counts.apple is inferred to be type number:

ts
const counts = {
apple: 1,
};
 
counts.apple;
(property) apple: number
ts
const counts = {
apple: 1,
};
 
counts.apple;
(property) apple: number

While counts is type { apple: number }, shouldn't TypeScript know that the immediately available value of counts.apple is specifically the literal type 1 and not the general primitive type number? Can't TypeScript tell we haven't changed the value yet?

It could, but it won't. And for very good reason.

· 20 min read

TypeScript's type system is Turing Complete: meaning it has conditional branching (conditional types) and works with an arbitrary huge amount of memory. As a result, you can use the type system as its own programming language complete with variables, functions, and recursion. Developers have pushed the bounds of type operations possible in the type system to write some pretty incredible things!

This blog post is a starting list of nifty things TypeScript developers have pushed the type system to be able to do. They range from binary arithmetic and rudimentary virtual machines to maze solvers and full programming languages.

✋ This Is Not Normal

Most applications try to get away with as few extreme type operations as possible. Complex logic in the type system gets unreadable and hard to debug pretty quickly. The Learning TypeScript book advises:

If you do find a need to use type operations, please—for the sake of any developer who has to read your code, including a future you—try to keep them to a minimum if possible. Use readable names that help readers understand the code as they read it. Leave descriptive comments for anything you think future readers might struggle with.

Please don't look at the following projects list and think you need to understand them to use TypeScript. These projects are ridiculous. They're the equivalent of code golf: a fun activity for a select few, but not useful for most day-to-day work.

· 5 min read

Most popular programming languages use the void keyword to indicate that a function cannot return a value. Learning TypeScript describes TypeScript's void keyword as indicating that the returned value from a function will be ignored. Those two definitions are not always the same! TypeScript's void comes with an initially surprising behavior: a function type with a non-void return is considered assignable to a function type with a void return.

ts
let returnsVoid: () => void;
 
returnsVoid = () => "this is fine";
ts
let returnsVoid: () => void;
 
returnsVoid = () => "this is fine";

Why is that?

· 20 min read

Finding the right names and values for small code snippets is a surprisingly challenging task. Sample content like foo bar and lorem ipsum gets boring quickly -- and can be confusing to readers who aren't yet familiar with them. It's better to have self-contained code snippets that can be clearly understood without any external context.

Crafting appropriate themed content that fits cleanly with the theory being demonstrated is fraught with danger. Code snippets shouldn't contain an egregious quantity of tangentially-related setup code just to justify their theme. Themes also shouldn't be unnecessarily flashy to the point of distracting from the important conceptual explanations.

Many authors opt to stick with a small set of themes they know to be flexible and work well. I personally go with names of fruit, such as counting amounts of them. While sticking with the same theme repeatedly is reliable, it can get boring for the reader fast.

That's why, at risk of distracting ever so slightly from code content, most chapters in Learning TypeScript use an overarching theme for most or all of its code snippets. Most of the themes are fairly straightforward, such as historical authors or inventors. Others are a little more subtle and act more like easter eggs.