BeCuriouzBeCuriouz

Learn something new every day

🔷 TypeScript
🏛️

The Habits That Separate Solid TypeScript from Fragile TypeScript

💡 Master TypeScript by making bad states impossible, validating at the boundaries, and treating every any, as, and ! as a debt you must justify.

By now you've met strict mode, generics, narrowing, and utility types. Mastery isn't knowing more features — it's a handful of habits applied consistently. The first: make illegal states unrepresentable. Instead of a Loading object with optional data, error, and isLoading fields (which allows nonsense like "loading and errored"), model it as a discriminated union so the compiler forbids the impossible.

ts
// fragile: 8 combinations, most invalid
type S = { isLoading: boolean; data?: User; error?: string };
// solid: exactly 3 valid states
type S =
  | { status: 'loading' }
  | { status: 'ok'; data: User }
  | { status: 'error'; error: string };

The second habit: type the boundaries, trust the interior. Data from fetch, JSON.parse, or localStorage arrives as any/unknown — validate it once at the edge (with a runtime check or a library like Zod), then let inference flow inward. Fragile code sprinkles as User casts everywhere; a cast is a promise to the compiler, not a check, so a wrong one crashes at runtime with the type system none the wiser.

The third: treat any and non-null ! as debt, not tools. Prefer unknown (which forces you to narrow before use) and let the compiler prove non-nullness through control flow. Enable noUncheckedIndexedAccess so arr[i] is T | undefined — because it genuinely might be. Solid TS lets the types do the reasoning; fragile TS overrides the types and hopes.

🏛️ The Habits That Separate Solid TypeScript from Fragile TypeScript — BeCuriouz