Programming

TypeScript Common Mistakes: Errors Developers Make and How to Fix Them

April 15, 2024
11 min read
By Saad Minhas
TypeScriptProgrammingErrorsBest PracticesType Safety
TypeScript Common Mistakes: Errors Developers Make and How to Fix Them

TypeScript is powerful, but it's easy to make mistakes that lead to frustrating errors. After years of using TypeScript in production, I've seen the same mistakes over and over. Here's how to avoid them.


Common TypeScript Mistakes


1. Overusing 'any' Type


The Problem:


// BAD - Defeats the purpose of TypeScript
function processData(data: any) {
  return data.value * 2; // No type safety!
}

Why It's Bad:


  • Loses all type safety
  • No autocomplete/IntelliSense
  • Errors only show at runtime

The Fix:


// GOOD - Define proper types
interface Data {
  value: number;
  name: string;
}

function processData(data: Data) {
  return data.value * 2; // Type-safe!
}

// Or use generics
function processData<T extends { value: number }>(data: T) {
  return data.value * 2;
}

// Use 'unknown' instead of 'any' when type is truly unknown
function processData(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: number }).value * 2;
  }
  throw new Error('Invalid data');
}

2. Not Using Strict Mode


The Problem:


TypeScript's default settings are too lenient, allowing many errors.


The Fix:


// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    // Or enable individual strict checks:
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

3. Confusing 'interface' and 'type'


The Problem:


Developers don't know when to use interface vs type.


The Difference:


// interface - can be extended and merged
interface User {
  name: string;
}

interface User {
  age: number; // Merges with previous declaration
}

// type - cannot be merged, but more flexible
type User = {
  name: string;
  age: number;
};

// type can use unions, intersections, primitives
type ID = string | number;
type Status = 'active' | 'inactive';

When to Use Each:


// Use interface for objects that might be extended
interface ComponentProps {
  title: string;
}

interface ButtonProps extends ComponentProps {
  onClick: () => void;
}

// Use type for unions, intersections, or computed types
type Theme = 'light' | 'dark';
type UserWithRole = User & { role: 'admin' | 'user' };

4. Not Handling Null/Undefined


The Problem:


// BAD - Can be null/undefined
function getName(user: User) {
  return user.name.toUpperCase(); // Error if user.name is null!
}

The Fix:


// GOOD - Handle null/undefined
function getName(user: User) {
  return user.name?.toUpperCase() ?? 'Unknown';
}

// Or with type guards
function getName(user: User | null) {
  if (!user) {
    return 'Unknown';
  }
  return user.name.toUpperCase();
}

// Use non-null assertion only when you're certain
function getName(user: User) {
  return user.name!.toUpperCase(); // Only if you're 100% sure
}

5. Incorrect Function Return Types


The Problem:


// BAD - Missing return type, async function returns Promise
async function fetchUser(id: number) {
  const response = await fetch(`/api/users/${id}`);
  return response.json(); // Returns Promise<any>
}

The Fix:


// GOOD - Explicit return types
interface User {
  id: number;
  name: string;
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<User>;
}

// Or better - validate the response
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  
  if (isUser(data)) {
    return data;
  }
  throw new Error('Invalid user data');
}

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

6. Not Using Generics Properly


The Problem:


// BAD - Not using generics
function getValue(obj: any, key: string) {
  return obj[key]; // Returns any
}

The Fix:


// GOOD - Use generics
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]; // Type-safe!
}

const user = { name: 'John', age: 30 };
const name = getValue(user, 'name'); // Type: string
const age = getValue(user, 'age'); // Type: number
// getValue(user, 'invalid'); // Error!

7. Forgetting to Export Types


The Problem:


Types aren't exported, so they can't be used in other files.


The Fix:


// Export types/interfaces
export interface User {
  id: number;
  name: string;
}

export type Status = 'active' | 'inactive';

// Re-export from other files
export type { User } from './user';
export type { ApiResponse } from './api';

8. Using 'as' Type Assertions Too Much


The Problem:


// BAD - Unsafe type assertion
const data = response.json() as User; // What if it's not a User?

The Fix:


// GOOD - Validate before asserting
function isUser(data: unknown): data is User {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    typeof (data as any).id === 'number' &&
    'name' in data &&
    typeof (data as any).name === 'string'
  );
}

const data = await response.json();
if (isUser(data)) {
  // Now TypeScript knows data is User
  console.log(data.name);
} else {
  throw new Error('Invalid user data');
}

9. Not Using 'const' Assertions


The Problem:


// BAD - Type is string[], not readonly tuple
const colors = ['red', 'green', 'blue'];

The Fix:


// GOOD - Use 'as const' for literal types
const colors = ['red', 'green', 'blue'] as const;
// Type: readonly ["red", "green", "blue"]

// Or for objects
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;
// Properties are readonly and literal types

10. Ignoring Type Errors with @ts-ignore


The Problem:


// @ts-ignore - Bad practice!
const value = someFunction(); // Ignoring real errors

The Fix:


// Fix the actual error instead
// If you must ignore, use @ts-expect-error with explanation
// @ts-expect-error - Legacy API, will be fixed in v2
const value = legacyFunction();

TypeScript Best Practices


1. Enable strict mode - Catch more errors

2. Avoid 'any' - Use 'unknown' or proper types

3. Use type guards - Validate data at runtime

4. Export types - Make them reusable

5. Use generics - Write reusable, type-safe code

6. Handle null/undefined - Use optional chaining

7. Use 'as const' - For literal types

8. Don't ignore errors - Fix them properly


Common TypeScript Errors and Fixes


Error: "Property 'x' does not exist on type 'y'"


// Fix: Add the property to the type
interface User {
  x: string; // Add missing property
}

Error: "Type 'string' is not assignable to type 'number'"


// Fix: Convert or validate type
const num: number = parseInt(str, 10);
// Or
const num: number = Number(str);

Error: "Object is possibly 'null'"


// Fix: Use optional chaining or null check
const value = obj?.property ?? defaultValue;
// Or
if (obj !== null) {
  const value = obj.property;
}

Conclusion


TypeScript mistakes are common but avoidable. The key is to:


1. Use strict mode - Catch errors early

2. Avoid 'any' - Use proper types

3. Handle null/undefined - Use optional chaining

4. Use generics - Write reusable code

5. Export types - Make them reusable

6. Fix errors properly - Don't ignore them


TypeScript is meant to help you, not frustrate you. Use it properly, and it will save you hours of debugging.

Get In Touch

Connect

Full Stack Software Engineer passionate about building innovative web and mobile applications.

CEO at Appzivo— a product and engineering studio for larger engagements.

© 2026 Saad Minhas. All Rights Reserved.