Back to Blog
TypeScriptJavaScriptProgramming

10 TypeScript Tips That Will Make You a Better Developer

Level up your TypeScript skills with these practical tips and patterns that I use every day.

December 10, 20243 min read

10 TypeScript Tips That Will Make You a Better Developer

After years of working with TypeScript, I've collected a set of patterns and tips that have significantly improved my code quality. Here are my favorites.

1. Use const Assertions for Literal Types

// Without const assertion
const config = { theme: "dark", version: 1 };
// type: { theme: string; version: number }
 
// With const assertion
const config = { theme: "dark", version: 1 } as const;
// type: { readonly theme: "dark"; readonly version: 1 }

2. Discriminated Unions for State Management

type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: User[] };
type ErrorState = { status: "error"; error: string };
 
type State = LoadingState | SuccessState | ErrorState;
 
function handleState(state: State) {
  switch (state.status) {
    case "loading":
      return <Spinner />;
    case "success":
      return <UserList users={state.data} />;
    case "error":
      return <Error message={state.error} />;
  }
}

3. Template Literal Types

type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// type Handler = "onClick" | "onFocus" | "onBlur"

4. The satisfies Operator

type Colors = Record<string, [number, number, number]>;
 
const palette = {
  red: [255, 0, 0],
  green: [0, 255, 0],
  blue: [0, 0, 255],
} satisfies Colors;
 
// palette.red is [number, number, number], not (number | string)[]

5. Branded Types for Type Safety

type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
 
function createUserId(id: string): UserId {
  return id as UserId;
}
 
function getUser(id: UserId) { /* ... */ }
 
const userId = createUserId("123");
const orderId = "456" as OrderId;
 
getUser(userId); // ✓ OK
getUser(orderId); // ✗ Error: Argument of type 'OrderId' is not assignable

6. Utility Types You Should Know

// Pick specific properties
type UserPreview = Pick<User, "id" | "name">;
 
// Omit specific properties
type UserWithoutPassword = Omit<User, "password">;
 
// Make all properties optional
type PartialUser = Partial<User>;
 
// Make all properties required
type RequiredUser = Required<User>;
 
// Extract return type
type FetchResult = ReturnType<typeof fetchUser>;

7. Generic Constraints

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
 
const user = { name: "John", age: 30 };
getProperty(user, "name"); // ✓ OK
getProperty(user, "email"); // ✗ Error

8. Conditional Types

type ApiResponse<T> = T extends Array<infer U>
  ? { items: U[]; total: number }
  : { data: T };
 
type UserResponse = ApiResponse<User>;
// { data: User }
 
type UsersResponse = ApiResponse<User[]>;
// { items: User[]; total: number }

9. Type Guards

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value
  );
}
 
function processData(data: unknown) {
  if (isUser(data)) {
    // data is now typed as User
    console.log(data.name);
  }
}

10. Mapped Types

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
interface Person {
  name: string;
  age: number;
}
 
type PersonGetters = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
// }

Conclusion

TypeScript's type system is incredibly powerful. Master these patterns and you'll write safer, more maintainable code.

Thanks for reading!

All Posts
Share: