How to Fix Common TypeScript Type Errors: Practical Guide With Examples
On this page
How to Fix Common TypeScript Type Errors: Practical Guide With Examples
TypeScript's type system is one of the most powerful tools available to JavaScript developers. It catches bugs before they reach production, improves code readability, and makes refactoring far less terrifying. But if you've spent any time writing TypeScript, you've almost certainly stared at a cryptic red squiggly line wondering what went wrong.
This guide walks through the most common TypeScript type errors you'll encounter, explains why they happen, and shows you exactly how to fix them with practical, real-world examples.
Understanding TypeScript Error Messages
Before diving into specific errors, it helps to understand how TypeScript communicates problems. Every error includes a code (like TS2322) and a message describing the mismatch. The key phrase to look for is "Type 'X' is not assignable to type 'Y'" — this tells you what TypeScript found versus what it expected.
When you see a long, nested error message, start reading from the bottom. The deepest line usually points to the exact property or type that caused the conflict.
Type 'X' Is Not Assignable to Type 'Y' (TS2322)
This is by far the most common TypeScript error. It means you're trying to assign a value of one type to a variable, parameter, or return type that expects something different.
// Error: Type 'string' is not assignable to type 'number'
let age: number = "twenty-five";
How to fix it: Ensure the value matches the declared type. Either change the value or update the type annotation.
let age: number = 25;
// OR, if a string is intentional:
let age: string = "twenty-five";
This error also appears frequently with object types when a property is missing or has the wrong type:
interface User {
name: string;
email: string;
}
// Error: Property 'email' is missing in type '{ name: string; }'
const user: User = { name: "Alice" };
Fix: Supply all required properties, or mark optional ones with ? in the interface.
interface User {
name: string;
email?: string; // now optional
}
Property Does Not Exist on Type (TS2339)
This error fires when you try to access a property that TypeScript doesn't know about on a given type.
const response = { status: 200, data: "OK" };
console.log(response.statusCode); // Error: Property 'statusCode' does not exist
How to fix it: Check for typos first — that's the most common cause. If the property genuinely exists at runtime but TypeScript doesn't know about it, you have a few options:
// Option 1: Fix the property name
console.log(response.status);
// Option 2: Use an index signature if the shape is dynamic
const response: { [key: string]: unknown } = fetchData();
console.log(response.statusCode);
// Option 3: Extend the type definition
interface ApiResponse {
status: number;
data: string;
statusCode?: number;
}
Object Is Possibly 'undefined' or 'null' (TS2532 / TS18048)
With strictNullChecks enabled (and it should be), TypeScript forces you to handle cases where a value might be null or undefined.
function getUser(id: number): User | undefined {
return users.find(u => u.id === id);
}
const user = getUser(1);
console.log(user.name); // Error: Object is possibly 'undefined'
How to fix it: Narrow the type with a check before accessing the property.
// Option 1: Guard clause
const user = getUser(1);
if (!user) {
throw new Error("User not found");
}
console.log(user.name); // Safe — TypeScript knows user is defined here
// Option 2: Optional chaining
console.log(user?.name);
// Option 3: Non-null assertion (use sparingly)
console.log(user!.name);
The non-null assertion operator (!) tells TypeScript "trust me, this isn't null." Use it only when you are genuinely certain. Overusing it defeats the purpose of strict null checks.
Argument of Type 'X' Is Not Assignable to Parameter of Type 'Y' (TS2345)
This appears when you pass an argument to a function that doesn't match the expected parameter type.
function greet(name: string) {
return `Hello, ${name}`;
}
greet(42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'
How to fix it: Convert the value or change the function signature.
greet(String(42)); // convert the argument
greet(42 as any); // escape hatch (avoid in production code)
A subtler version of this error occurs with object arguments:
function createUser(config: { name: string; role: "admin" | "user" }) {}
const config = { name: "Bob", role: "admin" };
createUser(config); // Error: string is not assignable to '"admin" | "user"'
TypeScript widens config.role to string because it's a mutable variable. Fix this with as const or an explicit type annotation:
const config = { name: "Bob", role: "admin" } as const;
// OR
const config: { name: string; role: "admin" | "user" } = { name: "Bob", role: "admin" };
Cannot Find Module or Its Type Declarations (TS2307)
This error means TypeScript can't locate the module you're importing, or it found the module but there are no type definitions for it.
import something from "some-library"; // Error: Cannot find module 'some-library'
How to fix it:
- Install the package if it's missing:
npm install some-library - Install type definitions if they exist:
npm install -D @types/some-library - Create a declaration file if no types exist. Add a
declarations.d.tsfile:
declare module "some-library" {
const value: any;
export default value;
}
- Check your tsconfig.json paths and
moduleResolutionsettings if the module is local.
Type Assertions and When to Use Them
Type assertions (as) tell the compiler to treat a value as a specific type. They're useful but dangerous if misused.
const input = document.getElementById("username") as HTMLInputElement;
console.log(input.value); // Works — TypeScript now treats it as an input element
When to use assertions:
- DOM element access where you know the element type
- Working with API responses that you've validated externally
- Migrating JavaScript code incrementally
When NOT to use assertions:
- To silence errors you don't understand
- As a substitute for proper type narrowing
- With
as anyscattered throughout production code
Handling Union Types and Type Narrowing
Union types are powerful but require narrowing before you can use type-specific features.
function format(value: string | number) {
return value.toFixed(2); // Error: Property 'toFixed' does not exist on type 'string'
}
Fix with type narrowing:
function format(value: string | number) {
if (typeof value === "number") {
return value.toFixed(2); // Safe
}
return value.toUpperCase(); // Also safe — TypeScript knows it's a string here
}
For custom objects, use discriminated unions:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
}
}
Generic Type Errors
Generics often produce confusing errors, especially for developers new to TypeScript.
function first<T>(arr: T[]): T {
return arr[0]; // Could be undefined if array is empty
}
A common mistake is over-constraining or under-constraining generic parameters:
// Too restrictive — won't accept numbers
function merge<T extends string>(a: T, b: T): string {
return a + b;
}
// Better — accepts any types that can be added
function merge<T extends string | number>(a: T, b: T) {
return `${a}${b}`;
}
When generic errors get confusing, try explicitly specifying the type parameter at the call site to see where the mismatch is:
const result = myFunction<string>("hello"); // makes the expected types clear
Excess Property Checks
TypeScript checks for extra properties when you assign object literals directly to typed variables.
interface Options {
color: string;
width: number;
}
// Error: 'height' does not exist in type 'Options'
const opts: Options = { color: "red", width: 100, height: 50 };
This only applies to direct object literal assignment. Assigning via a variable bypasses the check:
const raw = { color: "red", width: 100, height: 50 };
const opts: Options = raw; // No error
Fix: Either add the property to the interface, remove it from the object, or use an index signature if dynamic properties are expected.
Practical Tips for Debugging Type Errors
- Hover over variables in your editor. The inferred type tooltip is often more informative than the error message itself.
- Simplify the type. If a complex generic type is causing issues, temporarily replace it with a concrete type to isolate the problem.
- Use the
satisfiesoperator to check that a value conforms to a type without widening or narrowing it. - Read errors bottom-up. Deeply nested type errors show the root cause at the end.
- Avoid
anyas a fix. It silences the error but hides the bug. Useunknownif you need a permissive type and narrow from there. - Keep
strict: truein your tsconfig. It catches more errors upfront and prevents you from developing habits that lead to runtime bugs.
FAQ
Q: Should I use any to get rid of type errors quickly?
A: No. Using any disables type checking for that value entirely, which defeats the purpose of TypeScript. If you need flexibility, use unknown and narrow the type with runtime checks. Reserve any for genuine escape hatches during migration or when working with fundamentally untyped boundaries.
Q: What is the difference between type and interface?
A: Both define object shapes, but interface supports declaration merging (extending the same interface across files) and is generally preferred for object types. type is more versatile — it can represent unions, intersections, primitives, and mapped types. For most object definitions, either works fine.
Q: Why does TypeScript widen my string literal to string?
A: TypeScript assumes mutable variables might change, so it widens "admin" to string when you use let or assign to an object property. Use as const to preserve the literal type, or declare the variable with an explicit literal union type.
Q: How do I fix "Type 'X' has no properties in common with type 'Y'"? A: This means you're assigning an object that shares zero properties with the target type. Double-check that you're using the right variable or type. This often happens when you accidentally pass the wrong object to a function or mix up similar-looking interfaces.
Q: Is it bad practice to use the non-null assertion operator (!)?
A: It's not inherently bad, but it should be used sparingly. Every ! is a promise to the compiler that a value won't be null or undefined at runtime. If that promise is wrong, you get a runtime error — exactly the kind TypeScript is designed to prevent. Prefer proper null checks whenever possible.
Q: How do I handle third-party libraries that don't have type definitions?
A: First check if @types/<library-name> exists on npm. If it doesn't, create a local .d.ts declaration file with declare module "<library-name>" and add basic type information. You can start with any exports and refine the types over time as you learn the library's API.
Q: Why do I get errors when spreading objects into typed parameters?
A: Object spread creates a new object, and TypeScript may lose track of specific property types during the spread. Ensure the resulting object matches the target type. Sometimes you need an explicit type annotation on the spread result, or you can use satisfies to validate the shape without altering the inferred type.
Conclusion
TypeScript type errors can feel frustrating at first, but each one is preventing a potential runtime bug. The key to working effectively with TypeScript is understanding what the compiler is telling you and knowing the right patterns to resolve each situation. Start by reading error messages carefully, narrow your types explicitly, avoid any as a crutch, and lean on your editor's tooling to inspect inferred types. Over time, these errors become less of an obstacle and more of a helpful guide that keeps your code robust and maintainable.