DEV Community

Cover image for 20 TypeScript Tricks Every Developer Should Know πŸš€
Jagroop Singh
Jagroop Singh

Posted on

20 TypeScript Tricks Every Developer Should Know πŸš€

TypeScript is a powerhouse for modern JavaScript development, bringing type safety and advanced features to the table.
While many developers know the basics, there are hidden gems and practical tricks that can make your code more efficient, clean, and maintainable.
Let’s dive into 20 TypeScript tricks that every developer should know, with examples and practical approaches! πŸ’»


1. Non-Nullable Types (NonNullable) ❌

TypeScript provides the NonNullable utility to eliminate null and undefined from a type. This can help you avoid unexpected null values.

type User = { name: string; age?: number | null };
const user: NonNullable<User["age"]> = 30; // βœ… No null or undefined allowed
Enter fullscreen mode Exit fullscreen mode

2. Using Partial for Flexibility πŸ”§

The Partial<T> utility makes all properties in a type optional, which is great when you're updating only a subset of object fields.

interface User {
  name: string;
  age: number;
  email: string;
}

const updateUser = (user: Partial<User>) => {
  // You can pass only the fields you want to update
  return { ...user, updatedAt: new Date() };
};

updateUser({ name: 'John' }); // No need to provide the entire object
Enter fullscreen mode Exit fullscreen mode

3. Leverage Readonly for Immutable Data πŸ”’

When you need immutability in TypeScript, Readonly<T> makes all properties of a type immutable, preventing reassignment.

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

config.apiUrl = 'https://newapi.com'; // ❌ Error: Cannot assign to 'apiUrl' because it is a read-only property
Enter fullscreen mode Exit fullscreen mode

4. Mapped Types for Dynamic Property Typing πŸ› οΈ

Mapped types let you create new types by transforming existing ones. This is handy for creating variations of an object type.

type Status = 'loading' | 'success' | 'error';
type ApiResponse<T> = {
  [K in Status]: T;
};

const response: ApiResponse<string> = {
  loading: 'Fetching...',
  success: 'Data loaded',
  error: 'Something went wrong'
};
Enter fullscreen mode Exit fullscreen mode

5. Tuple Types with Optional Elements πŸ”’

Did you know TypeScript allows optional elements in tuples? This is great when dealing with variadic function arguments.

type UserTuple = [string, number?, boolean?];

const user1: UserTuple = ['Alice'];          // βœ… Just the name
const user2: UserTuple = ['Bob', 30];        // βœ… Name and age
const user3: UserTuple = ['Charlie', 25, true]; // βœ… Full tuple
Enter fullscreen mode Exit fullscreen mode

6. Union Types with Exhaustive Checks πŸ”

Ensure you're handling all possible cases with union types and exhaustive checks in switch statements.

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; // ❌ Error if a new status type is added but not handled
      return exhaustiveCheck;
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Utility Type Omit for Excluding Keys πŸ—‘οΈ

Sometimes you need to create an object type that excludes certain keys. Omit is your friend here!

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Omit<Todo, 'description'>;

const todo: TodoPreview = {
  title: 'Learn TypeScript',
  completed: false
};
Enter fullscreen mode Exit fullscreen mode

8. Type Narrowing with in and instanceof πŸ•΅οΈ

Use in and instanceof to narrow down types at runtime.

function processInput(input: string | number | { title: string }) {
  if (typeof input === 'string') {
    return input.toUpperCase(); // Narrowed to string
  } else if (typeof input === 'number') {
    return input * 2; // Narrowed to number
  } else if ('title' in input) {
    return input.title; // Narrowed to object with title property
  }
}
Enter fullscreen mode Exit fullscreen mode

9. Conditional Types for Advanced Type Logic πŸ”„

Conditional types give you incredible flexibility for transforming types based on conditions.

type IsString<T> = T extends string ? true : false;

type CheckString = IsString<'Hello'>; // true
type CheckNumber = IsString<42>; // false
Enter fullscreen mode Exit fullscreen mode

10. Use as const for Immutable Literal Types πŸ“œ

as const is great for freezing values and ensuring that TypeScript treats them as literal types, not mutable values.

const COLORS = ['red', 'green', 'blue'] as const;

type Color = typeof COLORS[number]; // 'red' | 'green' | 'blue'
Enter fullscreen mode Exit fullscreen mode

Follow me on github:

Jagroop2001 (Jagroop) Β· GitHub

"The best error message is the one that never shows up" - Jagroop2001

favicon github.com

11. Extract and Exclude to Refine Types 🧹

Use Extract and Exclude to filter out or pick specific types from a union.

type T = 'a' | 'b' | 'c';
type OnlyAOrB = Extract<T, 'a' | 'b'>; // 'a' | 'b'
type ExcludeC = Exclude<T, 'c'>; // 'a' | 'b'
Enter fullscreen mode Exit fullscreen mode

12. Type Guards for Custom Validation βœ…

Create your own type guards to refine types dynamically at runtime.

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

const value: any = 'Hello';

if (isString(value)) {
  console.log(value.toUpperCase()); // Safe: value is a string here
}
Enter fullscreen mode Exit fullscreen mode

13. Use Record for Dynamic Object Types πŸ“‹

When you need a type for an object with dynamic keys, Record<K, V> is the perfect fit.

type Role = 'admin' | 'user' | 'guest';
const permissions: Record<Role, string[]> = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read']
};
Enter fullscreen mode Exit fullscreen mode

14. Dynamic Class Properties with Index Signatures πŸ—οΈ

Index signatures allow you to create objects or classes with dynamically named properties.

class DynamicObject {
  [key: string]: any;
}

const obj = new DynamicObject();
obj.name = 'Alice';
obj.age = 30;
Enter fullscreen mode Exit fullscreen mode

15. never Type for Impossible States 🚫

The never type represents values that should never occur. It's commonly used in exhaustive checks.

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}
Enter fullscreen mode Exit fullscreen mode

16. Optional Chaining for Safe Property Access πŸ”—

Use optional chaining (?.) to safely access deeply nested properties without worrying about undefined errors.

const user = { profile: { name: 'John' } };
const userName = user?.profile?.name; // 'John'
const age = user?.profile?.age ?? 'Not provided'; // Fallback to default
Enter fullscreen mode Exit fullscreen mode

17. Default Values with Nullish Coalescing (??) πŸ”„

Use the nullish coalescing operator to provide a fallback value only if the original value is null or undefined.

const input: string | null = null;
const defaultValue = input ?? 'Default'; // 'Default'
Enter fullscreen mode Exit fullscreen mode

18. Inferring Return Types with ReturnType πŸ”„

The ReturnType<T> utility extracts the return type of a function, which can be helpful when you're dealing with complex types.

function getUser() {
  return { name: 'John', age: 30 };
}

type UserReturn = ReturnType<typeof getUser>; // { name: string; age: number; }
Enter fullscreen mode Exit fullscreen mode

19. Type Parameters in Functions πŸ§‘β€πŸ’»

Generic type parameters make your functions flexible and reusable across different types.

function identity<T>(value: T): T {
  return value;
}

identity<string>('Hello'); // 'Hello'
identity<number>(42); // 42
Enter fullscreen mode Exit fullscreen mode

20. Intersection Types to Combine Structures βž•

Intersection types let you combine multiple types into one.

type Admin = { privileges: string[] };
type User = { name: string };

type AdminUser = Admin & User;

const adminUser: AdminUser = {
  privileges: ['admin', 'editor'],
  name: 'Alice'
};
Enter fullscreen mode Exit fullscreen mode

These tricks will help you take your TypeScript skills to the next level! πŸ”₯ Keep experimenting and integrating these patterns into your projects for cleaner, more efficient code. Happy coding! πŸ˜ŽπŸ‘¨β€πŸ’»

Top comments (4)

Collapse
 
hraifi profile image
sewiko

I just discovered the Partial<T> utility, and I think it’s an impressive implementation. Instead of relying on a any keyword, it provides a clean and flexible way to create objects with optional properties.
Thanks for sharing.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @hraifi

Collapse
 
john12 profile image
john

Thanks for sharing !!
11. Extract and Exclude to Refine Types is very new to me and become my personal favorite.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @john12