TypeScript Best Practices

Chapter 2 of 7 · 6 practices · 4 min · 4-question quiz

Modeling data with types

Good types describe the states your data can really be in, and nothing else. This chapter covers unions, literal types, readonly data and where to write annotations.

Start: Annotate function boundaries and let TypeScript infer the rest

6 practices in this chapter

  1. 1Annotate function boundaries and let TypeScript infer the restTypeScript infers most local types correctly. Writing const count: number = 0 adds noise without adding safety. Annotations earn their place at boundaries, where they act as a contract: function…
  2. 2Model states as a discriminated union, not a bag of optional fieldsA type like { loading: boolean; data?: User; error?: string } allows states that make no sense: loading with an error, or neither data nor error after loading. Every consumer has to guess which…
  3. 3Make switches exhaustive with a never checkWhen you add a new variant to a union, every switch that handles it should fail to compile until it handles the new case. TypeScript can do this for you: after all cases are handled, the value's…
  4. 4Use literal unions and as const instead of loose strings and enumsA parameter typed string accepts typos. A union of string literals, such as 'asc' | 'desc', accepts only the values that work, and editors autocomplete them.
  5. 5Mark data you do not mean to change as readonlyreadonly properties and readonly T[] (or ReadonlyArray<T>) stop accidental mutation at compile time. They are most useful on: function parameters, so callers know you will not change their…
  6. 6Brand ids and units that share a primitive typeA userId and an orderId are both strings, so TypeScript happily lets you pass one where the other is expected. The same goes for cents and dollars, or milliseconds and seconds. These bugs pass…

Check yourself on this chapter

4 questions, right here once you are signed in. Your progress is saved as you read.

Sign up for the quiz