DEV Community

Jaydev Mahadevan
Jaydev Mahadevan

Posted on • Originally published at jaydevm.hashnode.dev

TypeScript Patterns to Level Up Your Game

Have you mastered the basics of TypeScript, and are wondering what to learn next? I was in the same position not long ago. Here are a few type patterns that will help you write high quality code and improve your productivity. I wish someone had showed me these when I was trying to improve my skills!

1. Partials

The Partial<T> utility type enables you to create a type with all properties of T set to optional. This is particularly useful when you want to partially update or copy an object.

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

// `changes` can be a combo of any property in `User`
function updateUser(id: string, changes: Partial<User>) {
  if (changes.name) {
    // Implementation to update name...
  }
  if (changes.age) {
    // Update age...
  }
}
Enter fullscreen mode Exit fullscreen mode

Using Partial<User> allows for the updateUser function to accept an object that may contain any subset of the properties defined in the User interface.

2. Records

Record<K,T> is a utility that constructs a type with a set of properties K of type T. This utility is ideal for mapping properties to values of a certain type, creating lookup tables, or any scenario where you need a more flexible yet strongly-typed object structure.

type CatInfo = {
  age: number;
  breed: string;
}

type CatTracker = Record<number, CatInfo>; // Cat ID to cat attrs

let cats: CatTracker = {};
// This is fine
cats[10] = { age: 5, breed: 'British Shorthair' }; 
// Error! 'tom' is not a number
cats['tom'] = { age: 5, breed: 'British Shorthair' };
Enter fullscreen mode Exit fullscreen mode

One way I use the Record type is to organize records pulled from a database when building a service API.

3. Discriminated Unions

What if you know exactly how your data should look ahead of time, and you want to limit the number of safe options?

Discriminated unions help you to do just that. This pattern is incredibly useful for state management and handling complex data structures safely.

// Define loading states as discriminated union
type LoadingState =
  | { status: 'loading' }
  | { status: 'success', data: string }
  | { status: 'error', error: Error };

function handleState(state: LoadingState) {
  switch (state.status) {
    case 'loading':
      // Handle loading...
      break;
    case 'success':
      // Handle success - `data` is guaranteed to be present
      const parsed = JSON.parse(state.data);
      // More logic...
      break;
    case 'error':
      // Handle `state.error`...
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Object Keys

The keyof operator in TypeScript is used to create a union type consisting of all the public property keys of a given type. Use it when you want to ensure that a string argument is a valid key for a given object type.

interface Person {
  name: string;
  age: number;
  hasPets: boolean;
}

// Using 'keyof' to create a union type of the keys of Person
type PersonKeys = keyof Person;

// PersonKeys is equivalent to: 'name' | 'age' | 'hasPets'

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const guy: Person = {
  name: 'John',
  age: 30,
  hasPets: true,
};

// Example usage
const nameKey: PersonKeys = 'name';
const personName: string = getProperty(guy, 'name'); // 'John'
Enter fullscreen mode Exit fullscreen mode

5. Read-Only Properties

The Readonly<T> utility type in TypeScript makes all properties of a type T immutable (in other words, cannot be reassigned after their initial declaration). This is particularly useful when you want to create a type that should not be modified after creation.

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

// Making User properties read-only
const user: Readonly<User> = {
  name: 'Alice',
  age: 30
};

// If `user` wasn't created as `Readonly`, the properties below would be mutable.

user.name = 'Bob'; // Error: Cannot assign to 'name' because it is a read-only property.
user.age = 25; // Error: Cannot assign to 'age' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

In Conclusion

These intermediate-level type demos above are super practical for everyday TypeScript usage. And this is just a sampling of everything that TypeScript can do. Check out the TypeScript docs to see all of the utility types available.

All in all, the more logic you can move into the type checker, the safer and more maintainable your code will be. Your team (and your future self) will thank you!

For more insights into TypeScript, full-stack development, and beyond, don't forget to subscribe to my newsletter!

Top comments (0)