Common TypeScript Errors & Fixes
TypeScript's compiler catches bugs before they reach production, but its error messages can be cryptic at first. This page walks through the most common errors you will encounter, explains why they happen, and shows you exactly how to fix them.
TS2345 — Argument of type X is not assignable to parameter of type Y
This error fires when you pass a value to a function whose type does not match the parameter's expected type. It's one of the most frequent TypeScript errors and usually means there's a genuine type mismatch.
// Error
function greet(name: string) {
console.log(`Hello, ${name}!`);
}
const user = { name: 'Alice', age: 30 };
greet(user);
// TS2345: Argument of type '{ name: string; age: number; }'
// is not assignable to parameter of type 'string'.Fix: Pass the correct type — in this case, extract the name property:
// Fix 1: Pass the right value
greet(user.name);
// Fix 2: Change the function to accept the object
function greetUser(user: { name: string }) {
console.log(`Hello, ${user.name}!`);
}
greetUser(user); // OKTS2322 — Type X is not assignable to type Y
Similar to TS2345 but occurs during assignment rather than function calls. You are storing a value in a variable or property that has an incompatible declared type.
// Error let age: number = "thirty"; // TS2322: Type 'string' is not assignable to type 'number'. // Also common with literal types type Direction = 'north' | 'south' | 'east' | 'west'; let dir: Direction = 'up'; // TS2322: Type '"up"' is not assignable to type 'Direction'.
// Fix: Use the correct type let age: number = 30; // Fix: Use a valid union member let dir: Direction = 'north'; // Fix: Widen the type if you genuinely need flexibility let flexAge: number | string = "thirty";
any and you later assign it to a typed variable. Use type guards or assertions carefully to bridge the gap.TS2339 — Property does not exist on type X
You are accessing a property that TypeScript does not know about on a given type. This often happens after JSON parsing, with third-party libraries, or when working with union types where only some members have the property.
// Error
interface Cat {
name: string;
meow(): void;
}
const pet: Cat = { name: 'Whiskers', meow() {} };
pet.bark();
// TS2339: Property 'bark' does not exist on type 'Cat'.
// Error with union type
function handle(input: string | number) {
console.log(input.toUpperCase());
// TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
// (number has no toUpperCase)
}// Fix 1: Add the property to the interface
interface Pet {
name: string;
meow?(): void;
bark?(): void;
}
// Fix 2: Narrow the union before accessing the property
function handle(input: string | number) {
if (typeof input === 'string') {
console.log(input.toUpperCase()); // Safe — narrowed to string
}
}
// Fix 3: Use a type assertion when you are certain (use sparingly)
const data = JSON.parse('{"foo": 1}') as { foo: number };
console.log(data.foo); // OKTS2571 — Object is of type 'unknown'
The unknown type is TypeScript's safer alternative to any. You can assign anything to unknown, but you can't do anything with it until you narrow its type.
This error appears when you try to use an unknown value without checking what it actually is.
// Error
function processInput(input: unknown) {
console.log(input.toUpperCase()); // TS2571
input.forEach((item: unknown) => {}); // TS2571 again
}
// Common in catch blocks (TypeScript 4+ makes caught errors 'unknown')
try {
riskyOperation();
} catch (err) {
console.log(err.message); // TS2571
}// Fix: Narrow with typeof / instanceof before use
function processInput(input: unknown) {
if (typeof input === 'string') {
console.log(input.toUpperCase()); // narrowed to string
}
if (Array.isArray(input)) {
input.forEach((item) => console.log(item)); // narrowed to array
}
}
// Fix for catch blocks
try {
riskyOperation();
} catch (err) {
if (err instanceof Error) {
console.log(err.message); // OK
} else {
console.log(String(err)); // fallback
}
}
// Create a reusable type guard helper
function isErrorWithMessage(err: unknown): err is { message: string } {
return (
typeof err === 'object' &&
err !== null &&
'message' in err &&
typeof (err as Record<string, unknown>).message === 'string'
);
}
try {
riskyOperation();
} catch (err) {
if (isErrorWithMessage(err)) {
console.log(err.message);
}
}unknown over any for function parameters that accept arbitrary input. It forces callers to narrow before use, making bugs impossible to hide.TS18048 — X is possibly 'undefined'
With strictNullChecks enabled (part of strict mode), TypeScript tracks undefined as a distinct type.
This error appears when you use a value that might be undefined without first confirming it exists.
// Error
const users = ['Alice', 'Bob', 'Charlie'];
// With noUncheckedIndexedAccess, array access returns string | undefined
const first = users[0];
console.log(first.toUpperCase());
// TS18048: 'first' is possibly 'undefined'.
// Also common with optional properties
interface Config {
timeout?: number;
}
function connect(config: Config) {
const ms = config.timeout * 1000;
// TS18048: 'config.timeout' is possibly 'undefined'.
}// Fix 1: Explicit undefined check
const first = users[0];
if (first !== undefined) {
console.log(first.toUpperCase()); // safe
}
// Fix 2: Non-null assertion (use only when you are certain it exists)
console.log(first!.toUpperCase());
// Fix 3: Optional chaining
console.log(first?.toUpperCase()); // returns undefined if first is undefined
// Fix 4: Nullish coalescing with a default
function connect(config: Config) {
const ms = (config.timeout ?? 5000) * 1000;
}
// Fix 5: Provide a fallback with logical OR
const name = users[0] || 'Unknown';! liberally — it silences the error but does not fix the underlying issue. Use it only when you have a guarantee TypeScript cannot infer.Property does not exist on type 'never'
The never type represents a value that can never occur — it's the bottom type, meaning a type that has been narrowed to nothing.
If TypeScript infers never for a variable, it means you have exhausted all possible types through narrowing, or there is a contradiction in your type logic.
// Example: narrowing leaves nothing
function process(input: string | number) {
if (typeof input === 'string') {
// input is string here
} else if (typeof input === 'number') {
// input is number here
} else {
// input is 'never' here — no type left
console.log(input);
// Property 'foo' does not exist on type 'never'
}
}
// Contradictory intersection
type Impossible = string & number; // never
// The type can never be satisfied// Understanding: 'never' in exhaustive checks is intentional
type Shape = 'circle' | 'square';
function area(shape: Shape): number {
if (shape === 'circle') return Math.PI;
if (shape === 'square') return 1;
// TypeScript knows we've handled all cases
// This assertion would error if Shape gains a new member — great for safety!
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
// If you are hitting 'never' unexpectedly, your narrowing is too aggressive
// Widen the type or remove the impossible branchnever in an unexpected place is TypeScript telling you that a code path is unreachable. This is often a sign of a logic error or an overly narrow type."Each member of the union type has signatures, but none are compatible"
This error occurs when you try to call a value typed as a union of functions, but the function signatures differ enough that TypeScript cannot find a safe common calling convention.
// Error
type StringFn = (x: string) => void;
type NumberFn = (x: number) => void;
type EitherFn = StringFn | NumberFn;
declare const fn: EitherFn;
fn('hello');
// Error: Each member of the union type has signatures,
// but none of those signatures are compatible with each other.
// Why: if fn is StringFn, it CANNOT accept a number.
// if fn is NumberFn, it CANNOT accept a string.
// TypeScript cannot safely call it with either argument type.// Fix 1: Use a union-parameter function
type FlexFn = (x: string | number) => void;
declare const flexFn: FlexFn;
flexFn('hello'); // OK
flexFn(42); // OK
// Fix 2: Use intersection for overloaded callables
type BothFn = ((x: string) => void) & ((x: number) => void);
declare const bothFn: BothFn;
bothFn('hello'); // OK
bothFn(42); // OK
// Fix 3: Narrow to one branch before calling
if (typeof myArg === 'string') {
(fn as StringFn)(myArg);
} else {
(fn as NumberFn)(myArg);
}"Type 'string' cannot be used as an index type"
This error appears when you use a string typed value to index an object that has specific known keys rather than a general string index signature.
// Error
const colors = { red: '#f00', green: '#0f0', blue: '#00f' };
function getColor(name: string) {
return colors[name];
// Error: Element implicitly has an 'any' type because
// expression of type 'string' can't be used to index
// type '{ red: string; green: string; blue: string; }'.
}// Fix 1: Use keyof to restrict the parameter
function getColor(name: keyof typeof colors) {
return colors[name]; // name is 'red' | 'green' | 'blue'
}
// Fix 2: Type guard with 'in'
function getColorSafe(name: string): string | undefined {
if (name in colors) {
return colors[name as keyof typeof colors];
}
return undefined;
}
// Fix 3: Add an index signature to the type
interface ColorMap {
[key: string]: string;
}
const dynamicColors: ColorMap = { red: '#f00' };
function getDynColor(name: string) {
return dynamicColors[name]; // OK
}
// Fix 4: Use Record<string, string>
const palette: Record<string, string> = { red: '#f00' };"Cannot redeclare block-scoped variable"
This error appears when you declare a variable with the same name twice in the same scope using let or const.
It also appears when TypeScript believes a global variable name conflicts with a local declaration — common in script files that have no import or export statements.
// Error: obvious re-declaration const username = 'Alice'; const username = 'Bob'; // Cannot redeclare block-scoped variable 'username'. // Subtle error: file treated as a global script // TypeScript treats the file as global, so 'name' conflicts with window.name: const name = 'Alice'; // Cannot redeclare block-scoped variable 'name'.
// Fix 1: Rename the variable
const username = 'Alice';
const updatedUsername = 'Bob';
// Fix 2: Make the file a module by adding an export or import
// This creates module scope and avoids global conflicts
export {}; // Turns the file into a module
const name = 'Alice'; // OK — module-scoped now
// Fix 3: Use a name that does not shadow globals
const userName = 'Alice'; // avoids conflict with 'name' globalimport or export statements. Without them, TypeScript treats the file as a global script."Module has no exported member"
This error occurs when you try to import a named export that does not exist in the target module. Common causes: typos, the export was removed or renamed, it is a default export, or the type definitions are outdated.
// Error
import { getUserById } from './userService';
// Module has no exported member 'getUserById'.
// The module actually exports:
// export function fetchUser(id: number) { ... }
// export default class UserService { ... }// Fix 1: Use the correct export name
import { fetchUser } from './userService';
// Fix 2: Import the default export
import UserService from './userService';
// Fix 3: Import everything and inspect at runtime
import * as UserModule from './userService';
// Fix 4: Re-export with the expected name (if you control the module)
// In userService.ts:
export { fetchUser as getUserById } from './userService';TS2304 — Cannot find name
TypeScript does not recognize a variable, type, or global name. Common causes are missing imports, typos, or missing lib configuration.
// Error: Missing import
const [value, setValue] = useState(false);
// TS2304: Cannot find name 'useState'.
// Fix: Add the import
import { useState } from 'react';
// Error: Using browser globals without DOM lib
document.getElementById('app');
// TS2304: Cannot find name 'document'.
// Fix: Add "dom" to lib in tsconfig.json
// "lib": ["ES2020", "DOM", "DOM.Iterable"]
// Error: Using Node.js globals without @types/node
process.env.NODE_ENV;
// TS2304: Cannot find name 'process'.
// Fix: Install @types/node
// npm install --save-dev @types/nodeTS2307 — Cannot find module
TypeScript cannot find a module you are importing — the module may not exist, the path may be wrong, or it may have no type definitions.
// Error: no type definitions for lodash
import lodash from 'lodash';
// Could not find a declaration file for module 'lodash'.
// Error: wrong path
import { utils } from '../helpers/util';
// Cannot find module '../helpers/util'.// Fix 1: Install @types package
// npm install --save-dev @types/lodash
import lodash from 'lodash'; // now has types
// Fix 2: Declare the module yourself
// Create a file: src/types/lodash.d.ts
declare module 'lodash' {
export function chunk<T>(array: T[], size: number): T[][];
}
// Fix 3: Wildcard declaration for untyped assets
declare module '*.png';
declare module '*.svg';
declare module 'some-untyped-lib';
// Fix 4: Check the path (case-sensitive on Linux/macOS)
import { utils } from '../helpers/utils'; // 'utils' not 'util'Quick Reference: Error Codes
Error Code | Description | Common Cause |
|---|---|---|
TS2345 | Wrong argument type | Passing object where primitive expected |
TS2322 | Wrong assignment type | Mismatched variable type or literal union |
TS2339 | Property does not exist | Typo, wrong type, or union not narrowed |
TS2571 | Object is unknown | catch(err) blocks, JSON.parse result |
TS18048 | Possibly undefined | Optional property or array access without check |
TS2304 | Cannot find name | Missing import, typo, or missing lib |
TS2307 | Cannot find module | Missing package, wrong path, or no @types |
TS2540 | Cannot assign to readonly | Attempting to mutate a readonly property |
TS7006 | Implicit any parameter | Function param has no type with noImplicitAny |
TS2554 | Wrong argument count | Too many or too few arguments passed |
TS2532 | Object possibly undefined | Similar to TS18048, object access |
TS2741 | Missing properties | Partial object assigned to required-field type |
General Debugging Strategy
Read the full error message — TypeScript always reports the expected type AND the actual type.
Hover over the variable in your editor to see its inferred type. This is faster than reading the error alone.
Use Go to Definition on the type to understand its shape fully.
Add a temporary type assertion: const _check: ExpectedType = value; — the error on this line pinpoints the mismatch.
Check if strict mode or a specific flag like noUncheckedIndexedAccess was recently enabled.
Search the TypeScript error code in the TypeScript GitHub issues for community explanations.
Use // @ts-expect-error (not @ts-ignore) to suppress errors you intend to fix — it warns if the error disappears.