Skip to main content
Back to home

Advanced TypeScript Tips for Better Code

By Merlin HardistyJanuary 10, 20263 min read

Why TypeScript?

TypeScript has become the de facto standard for building large-scale JavaScript applications. It catches errors early, improves code documentation, and makes refactoring much safer.

Advanced Tips

1. Use Const Assertions

Const assertions give you more precise types:

// Without const assertion
const config = {
  api: 'https://api.example.com',
  timeout: 5000
};
// Type: { api: string; timeout: number }

// With const assertion
const config = {
  api: 'https://api.example.com',
  timeout: 5000
} as const;
// Type: { readonly api: "https://api.example.com"; readonly timeout: 5000 }

2. Discriminated Unions

Create type-safe state machines:

type LoadingState = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; error: Error };

function handleState(state: LoadingState) {
  switch (state.status) {
    case 'idle':
      return 'Ready to load';
    case 'loading':
      return 'Loading...';
    case 'success':
      return `Data: ${state.data}`;
    case 'error':
      return `Error: ${state.error.message}`;
  }
}

3. Template Literal Types

Create powerful string types:

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts' | '/comments';
type Route = `${HTTPMethod} ${Endpoint}`;

// Route is now:
// "GET /users" | "POST /users" | "PUT /users" | ...

4. Utility Types

Master the built-in utility types:

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

// Make all properties optional
type PartialUser = Partial<User>;

// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit specific properties
type UserWithoutId = Omit<User, 'id'>;

// Make all properties readonly
type ReadonlyUser = Readonly<User>;

5. Branded Types

Create nominal types in a structural type system:

type UserId = string & { readonly brand: unique symbol };
type PostId = string & { readonly brand: unique symbol };

function getUserById(id: UserId) {
  // ...
}

const userId = "user-123" as UserId;
const postId = "post-456" as PostId;

getUserById(userId); // ✓ OK
getUserById(postId); // ✗ Error

Best Practices

Enable Strict Mode

Always use strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true
  }
}

Avoid Any

The any type defeats the purpose of TypeScript. Use unknown instead:

// Bad
function process(data: any) {
  return data.value; // No type checking
}

// Good
function process(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: unknown }).value;
  }
  throw new Error('Invalid data');
}

Use Type Guards

Create reusable type guards:

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

function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value
  );
}

Conclusion

TypeScript is a powerful tool that becomes even more valuable as you learn its advanced features. These tips will help you write more type-safe, maintainable code.