Back to All Blogs

10 TypeScript Tips for Writing Better Code

Master TypeScript with these practical tips that will help you write more maintainable, type-safe code and avoid common pitfalls.

5 min readBy Qtechs
TypeScriptJavaScriptWeb DevelopmentBest PracticesCode Quality

Introduction

TypeScript has revolutionized how we write JavaScript by adding static typing to the language. While it's powerful, many developers don't use it to its full potential. In this guide, we'll explore ten practical tips that will elevate your TypeScript code from good to great.

1. Use Strict Mode

Always enable strict mode in your tsconfig.json. This catches potential bugs early and enforces better coding practices.

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

Strict mode prevents common mistakes like implicit any types and helps you write more robust code.

2. Leverage Type Inference

TypeScript is smart enough to infer types in many situations. Don't over-annotate – let TypeScript do the work when it can.

Instead of this:

const name: string = "John";
const age: number = 30;

Write this:

const name = "John";  // TypeScript knows this is a string
const age = 30;       // TypeScript knows this is a number

Save explicit type annotations for function parameters, return types, and complex objects where clarity matters.

3. Use Interfaces for Object Shapes

When defining object structures, interfaces provide better error messages and are more extensible than type aliases.

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}
 
function createUser(user: User): User {
  return user;
}

Interfaces can also be extended and merged, making them ideal for defining contracts in your application.

4. Embrace Union Types

Union types let you express that a value can be one of several types, making your code more flexible and type-safe.

type Status = 'loading' | 'success' | 'error';
 
function handleResponse(status: Status) {
  switch (status) {
    case 'loading':
      return 'Loading...';
    case 'success':
      return 'Data loaded successfully';
    case 'error':
      return 'An error occurred';
  }
}

TypeScript will even warn you if you forget to handle a case!

5. Use Utility Types

TypeScript provides powerful utility types that can save you time and make your code more maintainable.

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
}
 
// Make all properties optional
type PartialProduct = Partial<Product>;
 
// Pick specific properties
type ProductPreview = Pick<Product, 'id' | 'name'>;
 
// Make all properties readonly
type ReadonlyProduct = Readonly<Product>;
 
// Omit specific properties
type ProductWithoutDescription = Omit<Product, 'description'>;

These utility types help you transform existing types without duplicating code.

6. Avoid Using 'any'

The any type defeats the purpose of TypeScript. If you're unsure about a type, use unknown instead.

// Bad
function processData(data: any) {
  return data.value; // No type checking
}
 
// Good
function processData(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: string }).value;
  }
  throw new Error('Invalid data');
}

unknown forces you to check the type before using it, making your code safer.

7. Use Const Assertions

Const assertions tell TypeScript to infer the most specific type possible, which is especially useful for configuration objects.

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
} as const;
 
// Now TypeScript knows these are literal types, not just string/number
// config.apiUrl is type 'https://api.example.com', not string

This prevents accidental modifications and provides better autocomplete.

8. Create Custom Type Guards

Type guards help TypeScript narrow down types within conditional blocks.

interface Dog {
  breed: string;
  bark(): void;
}
 
interface Cat {
  color: string;
  meow(): void;
}
 
function isDog(pet: Dog | Cat): pet is Dog {
  return (pet as Dog).bark !== undefined;
}
 
function handlePet(pet: Dog | Cat) {
  if (isDog(pet)) {
    pet.bark(); // TypeScript knows this is a Dog
  } else {
    pet.meow(); // TypeScript knows this is a Cat
  }
}

Custom type guards make your code more readable and type-safe.

9. Use Generics for Reusable Code

Generics allow you to write flexible, reusable code that works with multiple types.

function getFirstElement<T>(array: T[]): T | undefined {
  return array[0];
}
 
const firstNumber = getFirstElement([1, 2, 3]);     // type: number
const firstName = getFirstElement(['a', 'b', 'c']); // type: string

Generics maintain type safety while providing flexibility.

10. Document Complex Types

For complex types, add JSDoc comments to explain their purpose and usage.

/**
 * Represents a paginated API response
 * @template T - The type of items in the data array
 */
interface PaginatedResponse<T> {
  /** Array of items for the current page */
  data: T[];
  /** Current page number (1-indexed) */
  page: number;
  /** Total number of pages available */
  totalPages: number;
  /** Total number of items across all pages */
  totalItems: number;
}

Good documentation helps other developers (and your future self) understand your code.

Bonus Tip: Use ESLint with TypeScript

Integrate ESLint with TypeScript-specific rules to catch issues that TypeScript's compiler might miss.

npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin

This combination provides comprehensive code quality checks.

Conclusion

TypeScript is more than just adding types to JavaScript – it's about writing better, more maintainable code. These ten tips will help you leverage TypeScript's full power:

  1. Enable strict mode for better type safety
  2. Let TypeScript infer types when possible
  3. Use interfaces for object definitions
  4. Embrace union types for flexibility
  5. Leverage utility types to transform types
  6. Avoid any and use unknown instead
  7. Use const assertions for literal types
  8. Create custom type guards for better narrowing
  9. Use generics for reusable code
  10. Document complex types with JSDoc

Start incorporating these practices into your daily workflow, and you'll write TypeScript code that's more robust, maintainable, and enjoyable to work with.

Remember, TypeScript is a tool to help you catch errors early and write better code – embrace it, and your future self will thank you!

Q

Qtechs

Author

Related Posts

Why Next.js Is Perfect for Modern Web Apps

8th Feb 255 min read

Discover how Next.js combines performance, SEO, and developer experience to create lightning-fast web applications that scale.

Read More →

Getting Started with React Server Components

1st Feb 256 min read

Learn how React Server Components are revolutionizing web development by reducing bundle sizes and improving performance.

Read More →

Build a Full-Stack App with Next.js and Prisma

28th Jan 257 min read

A comprehensive guide to building modern full-stack applications using Next.js, Prisma, and PostgreSQL from scratch.

Read More →