Question
In TypeScript, why does this code produce the error Argument of type 'string' is not assignable to parameter of type 'never'?
const foo = (foo: string) => {
const result = []
result.push(foo)
}
I expected result to accept a string because foo is a string. What is TypeScript inferring here, why does this happen, and how should this code be written correctly? Is this a TypeScript bug or intended behavior?
Short Answer
By the end of this page, you will understand why TypeScript sometimes infers an empty array as never[], why that causes .push() to fail, and how to fix it by giving the array an explicit type or enough context for inference. You will also see how this fits into TypeScript's type inference rules in real projects.
Concept
TypeScript tries to infer types automatically from your code. When it sees this:
const result = []
there is no information about what kind of values the array should contain. In some contexts, TypeScript infers the type as never[].
never is a special TypeScript type that means this can never happen or no value is possible here. So a never[] means:
- this is an array
- but it can never contain any value
That is why this fails:
result.push(foo)
If result is inferred as never[], then push() expects values of type never. A string is not assignable to never, so TypeScript reports an error.
This is intended behavior, not a bug. TypeScript is being cautious because the empty array alone does not provide enough information about the element type.
To fix it, you need to tell TypeScript what the array should contain:
const result: string[] = []
or:
const result = [] as string[]
The first form is generally clearer and preferred.
Why this matters in real programming:
- Type inference is helpful, but it needs clues.
- Empty arrays often need explicit typing.
- Understanding
neverhelps you read confusing TypeScript errors more confidently. - This pattern appears often in reducers, accumulators, state initialization, and data transformation code.
Mental Model
Think of TypeScript as a very careful organizer labeling boxes.
If you write:
const result = []
you have given it an empty box with no label.
TypeScript asks:
- Should this box hold strings?
- numbers?
- objects?
- booleans?
Since it cannot safely guess, it may label the box as:
- a box that should never receive anything
That is what never[] means.
Then when you try to put a string into the box:
result.push(foo)
TypeScript says: "You told me this box accepts nothing, so I cannot allow a string either."
The solution is to label the box up front:
const result: string[] = []
Now TypeScript knows this box is for strings.
Syntax and Examples
Core idea
When creating an empty array in TypeScript, the element type may need to be provided explicitly.
Problem example
const foo = (foo: string) => {
const result = []
result.push(foo)
}
TypeScript may infer:
const result: never[]
So this becomes invalid:
result.push(foo) // string is not assignable to never
Correct ways to write it
1. Explicit array type
const foo = (foo: string) => {
const result: string[] = []
result.push(foo)
}
This is the clearest option. It tells TypeScript that result is an array of strings.
2. Type assertion
const foo = (foo: string) => {
const result = [] as string[]
result.push(foo)
}
This also works, but explicit type annotations are usually easier to read.
3. Provide inference from initial values
const result = ["hello"]
result.push("world")
Here TypeScript can infer string[] because the array already contains a string.
Another example with numbers
const values: number[] = []
values.push(1)
values.push(2)
If you leave off the type in a context where TypeScript cannot infer it, you may get the same never[] issue.
Function return example
const makeList = (item: string): string[] => {
const result: string[] = []
result.push(item)
return result
}
This is a common and correct pattern.
Step by Step Execution
Consider this code:
const addName = (name: string) => {
const names = []
names.push(name)
}
Here is what happens step by step:
-
TypeScript reads the function parameter:
name: stringSo
nameis known to be a string. -
TypeScript reads the empty array:
const names = []There are no elements, so there is no direct clue about the array's element type.
-
In this situation, TypeScript may infer:
const names: never[] -
TypeScript then checks this line:
names.push(name) -
If
namesisnever[], thenpush()only acceptsnevervalues. -
But
nameis astring, notnever. -
TypeScript reports:
Argument of type 'string' is not assignable to parameter of type 'never'.
Now compare it with the fixed version:
const addName = (name: string) => {
const names: string[] = []
names.push(name)
}
Step by step here:
nameis astring.namesis explicitly declared asstring[].push()expects astring.nameis astring.- The code type-checks successfully.
Real World Use Cases
This issue commonly appears in real code whenever you start with an empty array and fill it later.
1. Building a filtered result
const getShortNames = (names: string[]) => {
const result: string[] = []
for (const name of names) {
if (name.length < 5) {
result.push(name)
}
}
return result
}
2. Collecting validation errors
const validateUser = (name: string, age: number) => {
const errors: string[] = []
if (!name) errors.push("Name is required")
if (age < 18) errors.push("Must be at least 18")
return errors
}
3. Transforming API data
type User = { id: number; name: string }
const extractNames = (users: User[]) => {
const names: string[] = []
for (const user of users) {
names.push(user.name)
}
return names
}
4. Building log messages
const buildLogs = (items: number[]) => {
const logs: string[] = []
for (const item of items) {
logs.push(`Processed ${item}`)
}
return logs
}
In all of these cases, the array starts empty, so giving it an explicit type is often the safest choice.
Real Codebase Usage
In real projects, developers usually avoid this problem in a few common ways.
Explicitly type accumulators
This is very common in loops, reducers, and helper functions.
const ids: number[] = []
const messages: string[] = []
Use typed function return values
A return type can make the intent clearer.
const collectTags = (input: string[]): string[] => {
const tags: string[] = []
for (const item of input) {
tags.push(item.trim())
}
return tags
}
Use array methods that preserve type information
Methods like map and filter often avoid manual empty-array setup.
const names = users.map(user => user.name)
This is often simpler than manually pushing into an array.
Use guard clauses before adding values
const collectValidNames = (values: Array<string | undefined>) => {
const result: string[] = []
for (const value of values) {
if (!value) continue
result.push(value)
}
return result
}
Type object properties that start as empty arrays
type State = {
errors: string[]
}
const state: State = {
errors: []
}
This gives the empty array context, so TypeScript knows what it should hold.
In reducers, type the accumulator
const words = ["a", "bb", "ccc"]
const lengths = words.reduce<number[]>((acc, word) => {
acc.push(word.length)
return acc
}, [])
Without the generic type argument, the empty array initializer may not have enough context.
Common Mistakes
1. Assuming TypeScript will always infer the array element type
Broken example:
const result = []
result.push("hello")
Why it fails:
- The empty array gives no clear element type.
- TypeScript may infer
never[].
Fix:
const result: string[] = []
result.push("hello")
2. Using any[] just to silence the error
const result: any[] = []
result.push("hello")
result.push(123)
result.push(true)
Why this is risky:
- It removes useful type safety.
- Bugs become easier to introduce.
Better:
const result: string[] = []
3. Confusing never with unknown
nevermeans no value is possible.unknownmeans a value exists, but its type is not known yet.
These are very different.
4. Using type assertions too broadly
const result = [] as string[]
This works, but if overused, assertions can hide mistakes. Prefer annotations when possible:
const result: string[] = []
5. Forgetting to type nested empty arrays
Broken example:
type Group = {
names: string[]
}
const group = {
names: []
}
group.names.push("Sam")
Depending on the context, names may not get the type you expect.
Better:
const group: Group = {
names: []
}
6. Ignoring context differences
Sometimes TypeScript can infer correctly if there is enough context:
const names: string[] = []
or:
function buildNames(): string[] {
const names = []
names.push("A")
return names
}
But relying on inference in tricky cases can be confusing. Being explicit is often clearer.
Comparisons
never[] vs other array typings
| Type | Meaning | Can you push a string? | Typical use |
|---|---|---|---|
never[] | Array that should contain no possible values | No | Usually appears from inference when TypeScript has no safe element type |
string[] | Array of strings | Yes | Names, messages, labels |
number[] | Array of numbers | No | Scores, IDs, counts |
unknown[] | Array of unknown values | Yes, but reading values requires narrowing | Generic external data |
any[] | Array of anything | Yes | Usually avoided unless necessary |
never vs unknown vs any
| Type | Meaning | Safe? | Notes |
|---|---|---|---|
never | No value can exist | Yes, very strict | Often used in unreachable code or failed inference cases |
unknown | A value exists, but its type is not known yet | Yes | You must narrow before using it |
any | Skip type checking | No | Convenient, but removes protection |
Explicit type annotation vs assertion
| Approach | Example | Recommended? | Reason |
|---|---|---|---|
| Type annotation | const result: string[] = [] | Yes | Clear and safe |
| Type assertion | const result = [] as string[] | Sometimes | Works, but easier to misuse |
Manual push vs map
| Pattern | Example | When useful |
|---|---|---|
| Manual push | Build an array step by step | Good for conditional logic or loops |
map | Transform each item into a new array | Good when each input becomes one output |
Example:
const names = users.map(user => user.name)
This often avoids empty-array typing issues completely.
Cheat Sheet
Quick fix
If you see:
Argument of type 'X' is not assignable to parameter of type 'never'
and the code includes an empty array like this:
const result = []
try typing the array explicitly:
const result: string[] = []
Key idea
- Empty arrays may not provide enough type information.
- TypeScript may infer
never[]. never[]accepts no values.
Common fixes
const result: string[] = []
const values: number[] = []
const items: MyType[] = []
Alternative fix
const result = [] as string[]
Prefer this
const result: string[] = []
Meaning of never
never= impossible valuenever[]= array that cannot contain any element
Watch for these places
- accumulators
- reducers
- object properties initialized with
[] - state initialization
- helper functions building arrays in loops
Better inference with context
const result: string[] = []
or
const result = ["a"]
Avoid unless necessary
const result: any[] = []
This removes type safety.
FAQ
Why does TypeScript infer never[] for an empty array?
Because an empty array alone does not tell TypeScript what element type it should contain. In some contexts, the safest inference is never[].
Is not assignable to parameter of type never a TypeScript bug?
No. It is intended behavior based on TypeScript's type inference rules.
How do I fix parameter of type never when using .push()?
Give the array an explicit type, such as:
const result: string[] = []
Should I use as string[] or : string[]?
Usually : string[] is better because it is clearer and more explicit.
What does never mean in TypeScript?
never means no value can exist for that type. It is often used for impossible code paths or failed inference scenarios.
Why does this happen more often with empty arrays than non-empty arrays?
A non-empty array gives TypeScript sample values to inspect. For example, ["a"] clearly suggests string[], but [] gives no clue.
Can this happen with objects too?
Yes. Empty arrays inside objects can also need context or explicit typing.
Is using any[] a good fix?
Usually no. It hides the error but removes type safety. Prefer a specific array type instead.
Mini Project
Description
Build a small TypeScript utility that collects valid usernames from an input array. This demonstrates how to correctly type an initially empty array and safely push values into it after validation.
Goal
Create a function that returns a string[] of valid usernames without triggering never[] errors.
Requirements
- Write a function that accepts an array of strings or undefined values.
- Create an empty result array with the correct TypeScript type.
- Skip undefined values and blank strings.
- Add trimmed valid usernames to the result.
- Return the final array of usernames.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.