10 Advanced TypeScript Tricks for Experienced Developers:

Chandan Kumar
3 min readDec 30, 2023

--

Photo by Timothy Cuenat on Unsplash
  1. **Conditional Types:** Shape types based on conditions, creating dynamic type hierarchies. For example, defining a type that’s an `object` with specific properties depending on a provided string value.
type Config = {
type: 'basic' | 'advanced';
// ... other properties
};

type BasicConfig = Config & { type: 'basic' };
type AdvancedConfig = Config & { type: 'advanced' };

function configure(config: Config): void {
if (config.type === 'basic') {
console.log('Applying basic configuration');
// config is now of type BasicConfig
} else {
console.log('Applying advanced configuration');
// config is now of type AdvancedConfig
}
}

2. **Mapped Types:** Transform existing types by applying operations to each property. Useful for adding prefixes/suffixes to properties, creating key-value pairs from objects, etc.

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

type ReadOnly<T> = { readonly [P in keyof T]: T[P] };

const readOnlyUser: ReadOnly<User> = {
name: 'John Doe',
age: 30
};

// readOnlyUser.name = 'Jane Doe'; // Error: Cannot assign to 'name' because it is a read-only property

3. **Template Literal Types:** Build complex string types using dynamic expressions within backticks. Ideal for representing API responses, URL patterns, or validation formats.

type Endpoint = `/users/${number}`;

const validEndpoint: Endpoint = '/users/123';

// const invalidEndpoint: Endpoint = '/posts/456'; // Error: Type '"/posts/456"' is not assignable to type 'Endpoint'

4. **Higher-Order Types (HOTs):** Utilize functions that take and return types, enabling powerful abstractions like generic type predicates and type builders.

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

function logIfString<T>(value: T): T extends string ? void : T {
if (IsStringLiteral<T>) {
console.log(value);
}
return value;
}

5. **Recursive Types:** Define types that reference themselves, allowing creation of complex data structures like linked lists or trees.

interface LinkedList<T> {
value: T;
next?: LinkedList<T>;
}

const numbersList: LinkedList<number> = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: null
}
}
};

6. **Type Aliases:** Create reusable names for complex types, improving code readability and maintainability.

type UserResponse = {
id: number;
username: string;
email: string;
};

function fetchUser(): UserResponse {
// ...
}

7. **Unions and Intersections:** Combine multiple types using `|` and `&` operators, representing values that can be one or both types simultaneously.

type NumberOrString = number | string;

const value: NumberOrString = 123; // or value = "hello"

type Point = { x: number, y: number };
type LabeledPoint = Point & { label: string };

8. **Type Guards:** Narrow down the type of a variable within conditional statements using assertions like `typeof` or custom functions.

function isNumber(value: NumberOrString): value is number {
return typeof value === 'number';
}

if (isNumber(value)) {
console.log('Value is a number:', value);
} else {
console.log('Value is a string:', value);
}

9. **Decorators:** Extend the behavior of classes, properties, and methods with custom logic, enabling features like dependency injection, validation, or performance optimization.

function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key}()`);
return originalMethod.apply(this, args);
};
}

class MyClass {
@Log
greet() {
console.log('Hello!');
}
}

10. **Utility Types:** Utilize built-in and community-defined utilities like `Pick`, `Omit`, or `Partial` to manipulate the structure of existing types without manual effort.

// Pick a subset of properties
type Address = Pick<User

**Bonus:** Explore advanced features like `keyof` operator, discriminated unions, generics with conditional types, and advanced generics patterns.

**Remember:** Always use advanced features for clear code, not just complexity. Balance power with readability and maintainability.

I hope these tricks expand your TypeScript toolkit and inspire you to write even more robust and expressive code!

--

--