跪拜 Guibai
← Back to the summary

The 10 TypeScript Errors You Hit Every Day, Translated Into Plain English

The most frustrating part of writing TypeScript isn't learning the type system—it's the screen full of red squiggly lines. You know it's an error, but you can't understand what it's saying. Type 'string' is not assignable to type 'never'. I know every word in that sentence, but together they make no sense. I spent an afternoon organizing all the TS errors I've encountered in my projects and found that it always comes down to these 10. This time, it's not just about telling you how to fix them, but explaining why TypeScript throws these errors—once you understand the reason, you won't panic the next time you see them.


1. Type 'X' is not assignable to type 'Y'

Frequency: ⭐⭐⭐⭐⭐ (almost daily)

// ❌ Error
const status: 'active' | 'inactive' = 'pending';
// Type '"pending"' is not assignable to type '"active" | "inactive"'

In plain English: The value you provided is not within the allowed range of the type. TypeScript's type system is a "whitelist" mechanism—only the types you declared can be assigned; everything else is rejected.

Why does TypeScript do this? Because the purpose of a union type is to restrict the range of values. If you write 'active' | 'inactive', you are telling TS "this variable can only be one of these two values." If you pass 'pending', TS assumes there is a logic problem in your code—either you missed a state, or you assigned the wrong value.

Solution:

// ✅ Add the missing type
const status: 'active' | 'inactive' | 'pending' = 'pending';

The easiest trap to fall into: Data fetched from an API is string, but your type is a union type.

// ❌ The API returns a string, but your type is a union type
const res = await fetch('/api/user');
const data = await res.json(); // data.status is of type any or string
const status: 'active' | 'inactive' = data.status; // Error

// ✅ Option 1: Type assertion (if you are sure the backend returns the correct value)
const status = data.status as 'active' | 'inactive';

// ✅ Option 2: Runtime validation (safer)
if (data.status === 'active' || data.status === 'inactive') {
  const status = data.status; // TS automatically narrows the type
}

Option 2 is safer because if the backend suddenly returns an unexpected value (like 'deleted'), Option 1 will silently pass, while Option 2 will go to the else branch, allowing you to handle it.


2. Property 'X' does not exist on type 'Y'

Frequency: ⭐⭐⭐⭐⭐

// ❌ Error
const user = {};
console.log(user.name);
// Property 'name' does not exist on type '{}'

One-sentence reason: The type definition for the object does not have this property.

Solution:

// ✅ Option 1: Define an interface
interface User {
  name: string;
  age: number;
}
const user: User = { name: 'Zhang San', age: 25 };

// ✅ Option 2: Use Record
const user: Record<string, unknown> = {};
user.name = 'Zhang San';

3. Argument of type 'X' is not assignable to parameter of type 'Y'

Frequency: ⭐⭐⭐⭐

// ❌ Error
function greet(name: string) { return `Hello ${name}`; }
greet(123);
// Argument of type 'number' is not assignable to parameter of type 'string'

One-sentence reason: The type of the function parameter does not match the type of the value you passed.

Solution: Pass the correct type, or modify the function signature.

// ✅ Change the call
greet(String(123));

// ✅ Or change the function definition
function greet(name: string | number) { return `Hello ${name}`; }

4. Object is possibly 'undefined' / 'null'

Frequency: ⭐⭐⭐⭐

// ❌ Error
const arr = [1, 2, 3];
const first = arr.find(x => x > 10);
console.log(first.toFixed(2));
// Object is possibly 'undefined'

In plain English: This value might not exist; you must check it before using it.

Why does TypeScript do this? The return type of Array.find() is T | undefined—because the array might not have an element that meets the condition. TypeScript does not allow you to call a method on a value that might be undefined, because at runtime it would throw TypeError: Cannot read properties of undefined. This error is actually TS helping you prevent bugs.

Three solutions (from recommended to not recommended):

// ✅ Option 1: Optional chaining (most recommended, safe and concise)
console.log(first?.toFixed(2)); // Returns undefined if first is undefined, won't crash

// ✅ Option 2: if guard (when you need special handling for non-existent values)
if (first !== undefined) {
  console.log(first.toFixed(2)); // TS automatically narrows to number
} else {
  console.log('Not found');
}

// ⚠️ Option 3: Non-null assertion (only use when you are 100% sure the value exists)
console.log(first!.toFixed(2)); // Dangerous: crashes at runtime if actually undefined

Suggestion: Default to ?., use if when you need to handle the non-existent case, and only use ! when you can guarantee the value exists (e.g., you just assigned it).


5. Type 'string' is not assignable to type 'never'

Frequency: ⭐⭐⭐ (the most confusing error)

// ❌ Error
const arr = [];
arr.push('hello');
// Argument of type 'string' is not assignable to parameter of type 'never'

In plain English: The empty array was not given a type, so TypeScript inferred it as never[]—an array that "should never have elements."

Why is it inferred as never? This is the logic chain of TypeScript's type inference:

  1. You wrote const arr = [], without a type annotation
  2. TS tries to infer the type from the initial value—but the array is empty, with no elements to reference
  3. TS can only infer "no element type," which is never[]
  4. never is TypeScript's bottom type, representing "a type that cannot exist"
  5. When you push('hello'), TS finds that string is not never, and throws an error

Solution: Provide the type at declaration, telling TS what this array will hold in the future.

// ✅ Explicit type
const arr: string[] = [];
arr.push('hello'); // Works

// ✅ Mixed types
const arr: (string | number)[] = [];
arr.push('hello');
arr.push(42);

Extension: This is why there is an unwritten rule in the TypeScript community—always manually type empty arrays. Inference works fine for non-empty arrays ([1, 2, 3] can be inferred as number[]), but empty arrays will always trip you up.


6. Cannot find module 'X' or its corresponding type declarations

Frequency: ⭐⭐⭐

// ❌ Error
import dayjs from 'dayjs';
// Cannot find module 'dayjs' or its corresponding type declarations

One-sentence reason: This package does not have TypeScript type definitions.

Solution:

# Option 1: Install the community-maintained types package
npm install -D @types/dayjs

# Option 2: If the @types package doesn't exist, create a declaration file
# src/types/dayjs.d.ts
declare module 'dayjs' {
  const dayjs: any;
  export default dayjs;
}

Quick judgment: Try npm install -D @types/package-name first; most popular packages have one. If not, create a .d.ts declaration file.


7. Type 'X | Y' is not assignable to type 'X'

Frequency: ⭐⭐⭐

// ❌ Error
function getUser(id: number): User | null {
  return db.find(id);
}

const user: User = getUser(1);
// Type 'User | null' is not assignable to type 'User'

One-sentence reason: The function might return null, but you are assigning it to a variable that does not accept null.

Solution:

// ✅ Option 1: Handle the null case
const user = getUser(1);
if (!user) throw new Error('User not found');
// Here, user's type is automatically narrowed to User

// ✅ Option 2: Non-null assertion (when you are sure the value exists)
const user = getUser(1)!;

8. 'X' is declared but its value is never read

Frequency: ⭐⭐⭐

// ❌ Error (yellow squiggly line)
import { useState, useEffect, useCallback } from 'react';
// 'useCallback' is declared but its value is never read

One-sentence reason: You imported something but didn't use it.

Solution: Delete unused imports. VS Code shortcut Shift + Alt + O auto-cleans.

Control this behavior in tsconfig:

{
  "compilerOptions": {
    "noUnusedLocals": true,      // Error on unused variables
    "noUnusedParameters": true   // Error on unused parameters
  }
}

9. 'this' implicitly has type 'any'

Frequency: ⭐⭐

// ❌ Error
const obj = {
  name: 'Zhang San',
  greet() {
    setTimeout(function() {
      console.log(this.name);
      // 'this' implicitly has type 'any'
    }, 1000);
  }
};

One-sentence reason: The this of a regular function has an uncertain target; TypeScript doesn't know what it is.

Solution:

// ✅ Use an arrow function (inherits outer this)
const obj = {
  name: 'Zhang San',
  greet() {
    setTimeout(() => {
      console.log(this.name); // Works
    }, 1000);
  }
};

10. Type 'X' is not a valid JSX element

Frequency: ⭐⭐ (common in React projects)

// ❌ Error
function App() {
  const Component = condition ? Header : Footer;
  return <Component />;
  // Type 'typeof Header | typeof Footer' is not a valid JSX element
}

One-sentence reason: TypeScript is not sure that the type of this component is a valid React component.

Solution:

// ✅ Use a type assertion
const Component = (condition ? Header : Footer) as React.FC;

// ✅ Or use React.ElementType
const Component: React.ElementType = condition ? Header : Footer;

Quick Reference

Error Keyword Plain Translation Fastest Fix
not assignable to type Type mismatch Check the type definitions on both sides
does not exist on type Property missing Add the interface definition
possibly undefined/null Might be null Add ?. or if guard
type 'never' Empty array untyped Add type at declaration string[]
cannot find module Missing type declaration Install @types/package-name
declared but never read Imported but unused Delete it, or Shift+Alt+O
implicitly has type 'any' this target unclear Switch to arrow function
not a valid JSX element Component type wrong Use React.ElementType

Bookmark this. Next time you see a TypeScript error, check this table first—it will solve 80% of the red squiggly lines.


What's the most absurd TypeScript error you've ever encountered? Share in the comments, and let's see if we share the same pain.