[[TypeScript]]'s type system is famously unsound (meaning that, there exists code that passes type checks but would fail at runtime) but in practice, it works surprisingly well. You rarely encounter type-related runtime errors with a sufficiently well-structured typescript codebase.
This note highlights the examples where this may occur, and how to avoid them.
# The Basics
These are more in the realm of bugs and sloppy code:
- Misusing `any`. This is by design. With strict `tsconfig` configurations and linter rules, you don't encounter the code is forced to.
- Misusing `as` cast.
- Incorrect type guards and type assertions.
- Incorrect type definitions against a non-typescript library.
# Mutation
There are many examples of this, but once mutation is involved, one cannot guarantee type safety.
```typescript
type aNumber = { a: number };
type aStringNumber = { a: number | string };
const obj: aNumber = { a: 1 };
function mutate(input: aStringNumber) {
input.a = "1";
}
mutate(obj);
const numVariable: number = obj.a; // in reality, string.
```
# [[Covariance and Contravariance]]
`Child[]` can be casted to `Parent[]` without any casting. As arrays are mutable, this can introduce unsoundness.
```typescript
type Animal = "cat" | "dog" | "frog";
type Mammal = "cat" | "dog";
const mammals: Mammal[] = ["cat", "dog"];
const animals: Animal[] = mammals;
animals.push("frog");
const shouldBeMammal: Mammal = mammals[2];
```