TypeScript enhances JavaScript with type safety and powerful features, but many developers use only its basics. Here’s a guide to 20 advanced TypeScript tricks that can boost your productivity, code maintainability, and will help improve code efficiency and readability. Each trick is demonstrated with practical code examples.

1. NonNullable: Excludes null and undefined.

ts
12
type User = { name: string; age?: number | null };
const userAge: NonNullable<User['age']> = 30; // No null or undefined

2. Partial: Makes all properties optional.

ts
12345678910
interface User {
  name: string;
  age: number;
  email: string;
}
const updateUser = (user: Partial<User>) => ({
  ...user,
  updatedAt: new Date(),
});
updateUser({ name: 'John' });

3. Readonly: Enforces immutability.

ts
12345
const config: Readonly<{ apiUrl: string; retries: number }> = {
  apiUrl: 'https://api.com',
  retries: 5,
};
config.apiUrl = 'https://newapi.com'; // Error

4. Mapped Types: Transform existing types dynamically.

ts
1234567
type Status = 'loading' | 'success' | 'error';
type ApiResponse<T> = { [K in Status]: T };
const response: ApiResponse<string> = {
  loading: 'Loading...',
  success: 'Done',
  error: 'Error',
};

5. Optional Tuple Elements: Use variadic tuple types.

ts
12
type UserTuple = [string, number?, boolean?];
const user1: UserTuple = ['Rob']; // Only name

6. Union Exhaustiveness: Ensure all cases are handled.

ts
1234567891011121314
type Status = 'open' | 'closed' | 'pending';
function handleStatus(status: Status) {
  switch (status) {
    case 'open':
      return 'Opened';
    case 'closed':
      return 'Closed';
    case 'pending':
      return 'Pending';
    default:
      const exhaustiveCheck: never = status;
      return exhaustiveCheck;
  }
}

7. Omit: Remove properties from a type.

ts
1234567
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
type TodoPreview = Omit<Todo, 'description'>;
const todo: TodoPreview = { title: 'Learn TypeScript', completed: false };

8. Type Narrowing: Use in and instanceof to narrow types.

ts
1234
function processInput(input: string | number | { title: string }) {
  if (typeof input === 'string') return input.toUpperCase();
  if ('title' in input) return input.title.toUpperCase();
}

9. Conditional Types: Apply conditional logic.

ts
12
type IsString<T> = T extends string ? true : false;
type CheckString = IsString<'Hello'>; // true

10. Literal Types with as const:

ts
12
const COLORS = ['red', 'green', 'blue'] as const;
type Color = (typeof COLORS)[number]; // 'red' | 'green' | 'blue'

11. Extract and Exclude: Filter union types.

ts
12
type T = 'a' | 'b' | 'c';
type OnlyAOrB = Extract<T, 'a' | 'b'>; // 'a' | 'b'

12. Custom Type Guards:

ts
123
function isString(input: any): input is string {
  return typeof input === 'string';
}

13. Record: Dynamic object types.

ts
123456
type Role = 'admin' | 'user' | 'guest';
const permissions: Record<Role, string[]> = {
  admin: ['write'],
  user: ['read'],
  guest: ['view'],
};

14. Index Signatures: Add dynamic properties.

ts
12345
class DynamicObject {
  [key: string]: any;
}
const obj = new DynamicObject();
obj.name = 'Rob';

15. Never Type: For exhaustive checks.

ts
123
function assertNever(value: never): never {
  throw new Error(`Unexpected: ${value}`);
}

16. Optional Chaining:

ts
12
const user = { profile: { name: 'John' } };
const userName = user?.profile?.name; // 'John'

17. Null Coalescing (??):

ts
12
const input: string | null = null;
const defaultValue = input ?? 'Default'; // 'Default'

18. ReturnType: Infer function return types.

ts
1234
function getUser() {
  return { name: 'John', age: 30 };
}
type UserReturn = ReturnType<typeof getUser>;

19. Generics: Flexible function types.

ts
1234
function identity<T>(value: T): T {
  return value;
}
identity<string>('Hello'); // 'Hello'

20. Intersection Types: Combine multiple types.

ts
123
type Admin = { privileges: string[] };
type User = { name: string };
type AdminUser = Admin & User;

Summary

Each of these tips highlights ways to write cleaner, more reliable TypeScript code. Using these tricks, you can leverage TypeScript’s full type system for safer and more maintainable development.

Would you like me to explain any of these tricks in more detail? I can also provide additional examples or show how these patterns can be combined in real-world scenarios.