TypeScript
What happens when you tell the compiler what you believe about your code?
TypeScript is JavaScript with a belief system. You state what you believe about your code. The compiler checks those beliefs for contradictions. Those contradictions are type errors. Stating beliefs more precisely means more precise error messages — which makes debugging and maintenance easier.
Why It Compounds
Code must be understandable by programmers who join the team in the future and don't know the history. TypeScript makes implicit contracts explicit.
| Without Types | With Types |
|---|---|
| Runtime crashes reveal shape mismatches | Compiler catches them before running |
| Domain rules live in comments or tribal knowledge | Domain rules live in the type system |
| Refactoring is grep-and-pray | Refactoring is compiler-guided |
| API boundaries are trust-based | API boundaries are contract-based |
In a monorepo, TypeScript is the language AND the boundary enforcement mechanism. Type-first development makes the compiler the methodology — domain types first, then layer-by-layer constraint satisfaction.
Monorepo Config
TypeScript performance in monorepos lives or dies by project references. Get this wrong and you get cascading errors that tempt you into disabling safety — which makes everything worse.
| Setting | Rule | Why |
|---|---|---|
composite: true | On all library tsconfigs | Enables incremental builds, declaration emit |
declaration: true | Paired with composite | Downstream projects resolve from .d.ts, not source |
rootDir | Must align with include and outDir | Misalignment causes TS6059 in CI |
skipLibCheck | Never in root config | Masks declaration mismatches between libraries |
strict: true | Always | Loosening strict trades safety for silence |
Never import across lib boundaries via src/ paths. Always use the package alias. Cross-boundary src/ imports bypass project references and break the Nx project graph.
Error Decoder
| Error | Meaning | Fix |
|---|---|---|
| TS6305 | Importing a project whose .d.ts output is missing or stale | Ensure composite: true, build deps first, import via package alias |
| TS6059 | File is not under rootDir | Align rootDir, include, outDir in tsconfig.lib.json |
| TS2305 | Module has no exported member | Barrel surface changed but consumers still import old names |
See Engineering Quality Benchmarks for measurable type safety thresholds and Engineering Anti-Patterns for common TypeScript config violations.
Best Practices
- Union types over enums — enums generate runtime code, unions are zero-cost
- No
defaultin switch unless needed — it negates exhaustiveness checking extendsover intersections — intersections produce confusing error messages- Object literals only specify known properties
Pitfalls
| Pattern | Problem |
|---|---|
any | Disables type checking entirely — a single any propagates |
as type assertions | Overrides the compiler's judgment — dangerous unless narrowing from unknown |
Nested export * | Defeats tree-shaking, creates barrel blowouts in monorepos |
@ts-ignore | Silences errors without fixing them — use @ts-expect-error if truly needed |
Type Patterns
Keyof with Typeof
Modifications to the source object are handled by the compiler automatically.
const icons = {
rightArrow: "fake right arrow image",
leftArrow: "fake left arrow image",
billing: "fake billing image",
};
function Icon(props: { name: keyof typeof icons }) {
return icons[props.name];
}
typeof icons gives the object shape. keyof typeof icons gives 'rightArrow' | 'leftArrow' | 'billing'. Add a new icon, the union updates. Remove one, consumers break at compile time.
Assertion Functions
Assertion functions throw unless a condition is true, narrowing the type for all subsequent code.
function assertNumber(n: unknown): asserts n is number {
if (typeof n !== "number") {
throw new Error("Value wasn't a number: " + n);
}
}
Type guards narrow inside a code block. Assertion functions narrow for everything after the call.
Write automated tests for assertion functions — they bypass the compiler's judgment.
Covariance vs Contravariance
Assignability is covariant for values (narrow assigns to wide) but contravariant for function parameters (wide assigns to narrow).
type TakesLiteralString = (s: "lastLoginDate") => string;
function takesString(s: string): string {
return s;
}
// Safe: takesString accepts any string, including "lastLoginDate"
const testFunction: TakesLiteralString = takesString;
testFunction("lastLoginDate");
The variable restricts callers to 'lastLoginDate'. The underlying function handles any string. Narrower call site, wider implementation — safe by construction.
Overloading
Useful when return type depends on input shape.
interface User { id: number; name: string }
interface UserWithComments extends User { comments: { subject: string }[] }
function findUser(id: number): User;
function findUser(id: number, options: { withComments: true }): UserWithComments;
function findUser(id: number, options?: { withComments?: boolean }): User | UserWithComments {
const user = { id: 1, name: "Amir" };
if (options?.withComments) {
return { ...user, comments: [{ subject: "Ms. Fluff's 4th birthday" }] };
}
return user;
}
Dig Deeper
📄️ TsConfig
Compiler Options
📄️ Consuming APIs
Consuming APIs
📄️ Framework
Framework Code
📄️ Generics
Typescript Generics
📄️ tRPC
Replace REST with tRPC when using TypeScript.
📄️ Typescript Types
Typescript is Javascript with type safety.
📄️ Typescript
React and Typescript
Context
- Type-First Development — The compiler as methodology
- Engineering Quality Benchmarks — Type safety thresholds for monorepos
- Engineering Anti-Patterns — TypeScript config violations to detect
- Nx Monorepo — Build system that enforces boundaries
- Hexagonal Architecture — Layer structure TypeScript encodes
- Dev Environment — Tooling setup
- TsConfig Settings — Compiler options deep dive
Resources
Use Execute Program to get the fundamentals in your fingers.
- Patterns by UseCase
- Execute Program Advanced TS
- No BS TS
- Mind Map Diagram
- JSON to Typescript
- Zod — Runtime validation that generates types
- Tanstack Query — Type-safe data fetching