15 Must-Know TypeScript Features to Level Up Your Development Skills

Harish Kumar · · 509 Views

TypeScript has become the go-to tool for developers building scalable, maintainable JavaScript applications. Its advanced features go far beyond basic typing, giving developers tools to create highly reusable and type-safe code. This article explores 15 advanced TypeScript concepts to take your skills to the next level.

But before we dive in, if you're looking for an in-depth resource to strengthen your JavaScript foundations, check out my eBook: JavaScript: From ES2015 to ES2023. It covers modern JavaScript features in detail, ensuring you're fully equipped to use TypeScript alongside the latest JavaScript standards.

👉 Download Javascript: from ES2015 to ES2023 - eBook

15 Must-Know TypeScript Features to Level Up Your Development Skills

1. Mapped Types

Mapped types allow you to transform the properties of a type into a new type. This is particularly useful for creating variations of an existing type.

type ReadOnly<T> = { readonly [K in keyof T]: T[K] };
type Optional<T> = { [K in keyof T]?: T[K] };

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

type ReadOnlyUser = ReadOnly<User>; // { readonly id: number; readonly name: string; }
type OptionalUser = Optional<User>; // { id?: number; name?: string; }

Mapped types are foundational for creating utility types and dynamic type manipulations.

2. Generics

Generics allow you to define reusable, type-safe components by introducing a placeholder type that can be specified later.

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

const stringResult = identity<string>("Hello");
const numberResult = identity<number>(42);

Generics make your functions, classes, and interfaces more versatile by adapting to various types without sacrificing type safety.

3. Generics with Type Constraints

You can constrain a generic to a certain shape or base type using the extends keyword, ensuring the passed type meets specific criteria.

interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello"); // Valid
logLength([1, 2, 3]); // Valid
// logLength(42); // Error: number does not have a length property

This ensures that your generic types meet specific requirements. This feature is particularly useful when working with objects that share a common property.

4. Generic Interfaces

Generic interfaces allow you to create reusable and flexible type definitions.

interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

const pair: KeyValuePair<string, number> = { key: "age", value: 30 };

This approach is ideal for designing libraries or APIs where types can vary but maintain consistency. By defining placeholder types, you can ensure the interface adapts to various data types while maintaining consistency.

5. Conditional Types

Conditional types enable logic within types, making them dynamic based on conditions.

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

type Result1 = IsString<string>;  // true
type Result2 = IsString<number>;  // false

This is particularly useful in type inference and narrowing down types dynamically. It is especially useful for building types that adapt to specific scenarios, such as APIs or utility types. 

6. infer Keyword

The infer keyword works within conditional types to extract or infer a type.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function example(): string {
  return "Hello, TypeScript!";
}

type ExampleReturn = ReturnType<typeof example>; // string

This is often used in libraries to extract types dynamically and improve type inference.

7. Type Variance

Type variance controls the relationships between more specific and broader types in generics. TypeScript supports covariance (subtypes flow downward) and contravariance (supertypes flow upward).

class Animal {}
class Dog extends Animal {}

let animalHandler: (animal: Animal) => void;
let dogHandler: (dog: Dog) => void;

dogHandler = animalHandler; // Valid (contravariance)
// animalHandler = dogHandler; // Error: Not all Animals are Dogs

Understanding type variance is crucial when working with generics, callbacks, and inheritance.

8. Utility Types

TypeScript provides built-in utility types like Partial, Pick, Omit, and Record that simplify type transformations.

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

type PartialUser = Partial<User>; // All fields optional
type UserIdName = Pick<User, "id" | "name">; // Only id and name
type NoIdUser = Omit<User, "id">; // All fields except id
type UserRecord = Record<string, User>; // Object with string keys and User values

These types are invaluable for reducing boilerplate and simplifying common type manipulations.

9. Keyof and Lookup Types

The keyof operator generates a union of an object’s keys, while lookup types retrieve the type of a property by key.

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

type UserKeys = keyof User; // "id" | "name"
type NameType = User["name"]; // string

These features make working with object types much more dynamic and flexible.

10. Reflections and Metadata

TypeScript doesn’t natively support runtime type reflection, but tools like reflect-metadata enable it through decorators.

import "reflect-metadata";

function LogType(target: any, propertyKey: string) {
  const type = Reflect.getMetadata("design:type", target, propertyKey);
  console.log(`${propertyKey} type: ${type.name}`);
}

class Example {
  @LogType
  public value!: string;
}
// Output: value type: String

Reflection is widely used in frameworks like Angular and NestJS for dependency injection and runtime validation.

11. Dependency Injection

Dependency injection is a design pattern that promotes modularity by injecting dependencies instead of creating them directly. TypeScript's decorators and strong typing make this pattern seamless.

class Logger {
  log(message: string) {
    console.log(message);
  }
}

class Service {
  constructor(private logger: Logger) {}

  doWork() {
    this.logger.log("Work done!");
  }
}

const logger = new Logger();
const service = new Service(logger);
service.doWork(); // Outputs: Work done!

This is fundamental in frameworks like Angular and NestJS.

12. Intersection and Union Types

Intersection (&) types merge two or more types, while union (|) types represent one of multiple types.

type A = { name: string };
type B = { age: number };

type C = A & B; // { name: string; age: number }
type D = A | B; // { name: string } | { age: number }

Union and intersection types are essential for modeling complex relationships in TypeScript.

13. Type Guards and in Operator

Type guards refine types during runtime checks, improving type safety.

function isString(value: unknown): value is string {
  return typeof value === "string";
}

if (isString("Hello")) {
  console.log("It's a string!");
}

type Animal = { name: string };
type Dog = { name: string; bark(): void };

function isDog(animal: Animal | Dog): animal is Dog {
  return "bark" in animal;
}

Type guards are powerful for narrowing union types dynamically.

14. Template Literal Types

Template literal types allow you to construct new string literal types dynamically.

type Event = "click" | "focus" | "hover";
type EventHandlers = `on${Capitalize<Event>}`; // "onClick" | "onFocus" | "onHover"

This is useful for creating flexible and expressive types, especially for APIs and frameworks.

15. Decorators

Decorators are experimental features used for annotating and modifying classes, methods, or properties.

function Log(target: any, key: string) {
  console.log(`${key} was accessed`);
}

class Example {
  @Log
  sayHello() {
    console.log("Hello, world!");
  }
}

Decorators are heavily used in frameworks like Angular.

A Final Note

Mastering these advanced TypeScript concepts will allow you to build type-safe, scalable applications. But don't stop there—if you want to strengthen your JavaScript fundamentals alongside modern TypeScript skills, consider my eBook: JavaScript: From ES2015 to ES2023. It’s the ultimate guide to mastering modern JavaScript, covering essential topics like async/await, modules, and advanced features like proxies and decorators.

Would you like to explore more about TypeScript, JavaScript, or how the two can be combined effectively? Let me know!

👉 Download Javascript: from ES2015 to ES2023 - eBook

15 Must-Know TypeScript Features to Level Up Your Development Skills
0

Please login or create new account to add your comment.

0 comments
You may also like:

JavaScript Best Practices: Tips for Writing Clean and Maintainable Code

JavaScript is one of the most versatile and widely used programming languages today, powering everything from simple scripts to complex web applications. As the language continues (...)
Harish Kumar

Ditch jQuery: Vanilla JS Alternatives You Need to Know

jQuery revolutionized web development by simplifying DOM manipulation, event handling, and animations. However, modern JavaScript (ES6 and beyond) now provides many built-in methods (...)
Harish Kumar

Shallow Copy vs Deep Copy in JavaScript: Key Differences Explained

When working with objects and arrays in JavaScript, it's crucial to understand the difference between shallow copy and deep copy. These concepts dictate how data is duplicated (...)
Harish Kumar

A Beginner’s Guide to Efficient Memory Use in JavaScript

Managing memory efficiently in JavaScript applications is essential for smooth performance, especially for large-scale or complex applications. Poor memory handling can lead to (...)
Harish Kumar

Exploring the New Features of JavaScript ES2024: A Look into the Future of Web Development

Discovering new functionality in programming languages is a bit like a holiday — it’s filled with anticipation and the excitement of exploring something new. With the proposed (...)
Harish Kumar

Understanding the `.reduce()` Method in JavaScript

The .reduce() method in JavaScript is one of the most powerful array methods used for iterating over array elements and accumulating a single value from them. Whether you're summing (...)
Harish Kumar