This TypeScript error occurs when you try to assign a value to a variable or array that TypeScript has inferred as the 'never' type. This commonly happens with empty arrays, exhaustive type narrowing, or incorrect type annotations. The fix involves providing explicit type annotations or adjusting your code logic.
The 'never' type in TypeScript represents values that never occur. It's the bottom type in TypeScript's type system—a type that is assignable to every other type, but no type is assignable to 'never' (except 'never' itself). When TypeScript infers a variable, parameter, or array as 'never', it means TypeScript has determined that no valid value should exist for it. This commonly happens in three scenarios: 1. **Empty arrays**: When you declare an empty array without an explicit type, TypeScript infers it as `never[]`, meaning an array that can contain nothing 2. **Exhaustive type narrowing**: When you've narrowed a union type down to the point where no possible values remain 3. **Impossible code paths**: When TypeScript determines a code branch should be unreachable based on type analysis The error message "Expression is not assignable to 'never'" indicates you're trying to assign a value to something TypeScript believes should never have a value. This is TypeScript's way of catching logical inconsistencies in your code.
The most common cause is declaring an empty array without specifying its type. TypeScript infers it as never[], which cannot accept any values.
// BEFORE: Error - cannot push to never[]
const items = [];
items.push("hello"); // Error: Argument of type 'string' is not assignable to parameter of type 'never'
// AFTER: Explicitly type the array
const items: string[] = [];
items.push("hello"); // OK
// Or use initial value to help TypeScript infer
const items = ["initial"]; // Inferred as string[]
items.push("hello"); // OK
// For arrays of objects
const users: { name: string; id: number }[] = [];
users.push({ name: "Alice", id: 1 }); // OK
// Or with a type alias
type User = { name: string; id: number };
const users: User[] = [];
users.push({ name: "Alice", id: 1 }); // OKThis fix applies to React state, class properties, and any empty array declaration.
When you narrow a union type too aggressively, TypeScript may eliminate all possibilities, resulting in 'never'.
// BEFORE: Over-narrowing creates 'never'
type Status = "pending" | "success" | "error";
function handleStatus(status: Status) {
if (status !== "pending" && status !== "success" && status !== "error") {
// TypeScript knows this is impossible - status is 'never' here
console.log(status); // Type is 'never'
}
}
// BETTER: Use exhaustive checks properly
function handleStatus(status: Status) {
if (status === "pending") {
console.log("Pending...");
} else if (status === "success") {
console.log("Success!");
} else if (status === "error") {
console.log("Error!");
} else {
// This should never execute - use for exhaustiveness checking
const _exhaustive: never = status;
throw new Error(`Unhandled status: ${_exhaustive}`);
}
}
// Example with contradictory type guards
type Result = { success: true; data: string } | { success: false; error: string };
function process(result: Result) {
// WRONG: This creates impossible condition
if (result.success === true && result.success === false) {
// result is 'never' here - impossible state
console.log(result);
}
// CORRECT: Use proper narrowing
if (result.success) {
console.log(result.data); // TypeScript knows success=true case
} else {
console.log(result.error); // TypeScript knows success=false case
}
}In React, untyped empty arrays in state or props frequently cause this error.
// BEFORE: React state inferred as never[]
import { useState } from "react";
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = (text: string) => {
setTodos([...todos, { id: Date.now(), text }]);
// Error: Type '{ id: number; text: string; }' is not assignable to type 'never'
};
return <div>...</div>;
}
// AFTER: Explicitly type the state
import { useState } from "react";
type Todo = { id: number; text: string };
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const addTodo = (text: string) => {
setTodos([...todos, { id: Date.now(), text }]); // OK
};
return <div>...</div>;
}
// For complex state shapes
type AppState = {
users: User[];
posts: Post[];
loading: boolean;
};
const [state, setState] = useState<AppState>({
users: [],
posts: [],
loading: false,
});This also applies to component props with array types.
Generic types with conflicting constraints can resolve to 'never'.
// BEFORE: Impossible generic constraint
type Impossible<T extends string & number> = T; // T is 'never' - nothing can be both string AND number
function process<T extends string & number>(value: T) {
return value; // T is 'never' here
}
// AFTER: Use union instead of intersection when appropriate
type StringOrNumber<T extends string | number> = T; // OK - T can be string OR number
function process<T extends string | number>(value: T) {
return value; // OK
}
// Example with array methods
const numbers = [1, 2, 3];
const filtered = numbers.filter((n) => n > 5); // May be empty, but still number[]
// If you further narrow incorrectly:
const items: (string | number)[] = ["a", 1];
const result = items.filter((x): x is string => typeof x === "string")
.filter((x): x is number => typeof x === "number");
// result is 'never[]' - nothing can be both string AND numberIf TypeScript's inference is incorrect and you're certain about the type, you can use a type assertion. Use this sparingly.
// BEFORE: TypeScript incorrectly infers never
const data = JSON.parse(response); // Type is 'any'
const filtered = data.filter((x: any) => x.active); // Might be inferred as never[]
// AFTER: Assert the correct type
type User = { id: number; name: string; active: boolean };
const data = JSON.parse(response) as User[];
const filtered = data.filter((x) => x.active); // Type is User[]
// Alternative: Use type guard
function isUserArray(value: unknown): value is User[] {
return Array.isArray(value) && value.every(
(item) => typeof item === "object" &&
"id" in item &&
"name" in item
);
}
const data = JSON.parse(response);
if (isUserArray(data)) {
const filtered = data.filter((x) => x.active); // OK
}Prefer proper type annotations over assertions when possible.
### The 'never' Type in TypeScript's Type System
The 'never' type is TypeScript's bottom type, representing computations that never produce a value. It's used in:
1. Exhaustiveness checking:
type Action = { type: "increment" } | { type: "decrement" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
// action is 'never' here if all cases are handled
const _exhaustive: never = action;
throw new Error(`Unhandled action: ${_exhaustive}`);
}
}If you later add a new action type but forget to handle it, TypeScript will error because the new type isn't assignable to 'never'.
2. Conditional types that can't be satisfied:
type IsString<T> = T extends string ? true : false;
type OnlyStrings<T> = T extends string ? T : never;
type Result1 = OnlyStrings<"hello">; // "hello"
type Result2 = OnlyStrings<42>; // never
type Result3 = OnlyStrings<string | number>; // string (never is filtered out in unions)3. Functions that never return:
function throwError(message: string): never {
throw new Error(message);
// No return statement - execution never completes normally
}
function infiniteLoop(): never {
while (true) {
// Infinite loop - never returns
}
}### Empty Array Inference Deep Dive
TypeScript infers empty arrays as never[] by design—it's the only type that's safely assignable to any other array type:
// This works because never[] is assignable to string[]
const strings: string[] = [];
// If it were any[], this would compile but fail at runtime
const nums: number[] = ["oops"] as any[]; // Type system circumventedThe never[] inference is a safety feature that forces you to be explicit about what you intend to store.
### Union Type Distribution
In union types, 'never' is automatically filtered out:
type Example = string | never | number; // Simplifies to: string | number
type Filtered<T> = T extends string ? never : T;
type Result = Filtered<string | number | boolean>; // number | booleanThis makes 'never' useful for filtering types in mapped types and conditional types.
### React Hook Dependencies
// Common in useEffect/useCallback/useMemo
const [items, setItems] = useState([]); // never[]
useEffect(() => {
// items is never[] - TypeScript won't autocomplete array methods properly
console.log(items.length);
}, [items]);
// Better:
const [items, setItems] = useState<string[]>([]);Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript