Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I am new to ts and have just started migrating my app from js to ts.
first function - calcAvg, takes an array of numbers and returns their average.
second function - takes a list of objects. each object has daily statistics (see DailyData interface).
TS marks calcAvg(marginArray) as an error - "Argument of type '(number | null)[]' is not assignable to parameter of type 'number[]'.
Type 'number | null' is not assignable to type 'number'.
Type 'null' is not assignable to type 'number'.ts(2345)
const marginRevArray: (number | null)[]"
Before calcAvg , I am checking the array for nulls and if there is one or more nulls values I don't calculate the average.
Any ideas what am I missing here?
Thanks!
*** see code below
interface DailyData {
id: number
cost: number
margin: number
const calcAvg = (dataList: number[]) => {
const reducer = (accumulator: number, curr: number) => accumulator + curr;
const dataSum = dataList.reduce(reducer, 0);
return dataSum / dataList.length;
const avgMargin = (dailyData: DailyData[]) => {
const marginArray = dailyData.map((obj: DailyData) => obj.cost > 0 ? obj.margin/obj.cost: null);
if (marginArray.some((el: number|null) => el=== null)) {
//if the array has null values don't calculate
return 'no data';
return calcAvg(marginArray);
–
–
–
–
The TypeScript standard library call signature for Array
's some()
method looks like
interface Array<T> {
some(
predicate: (value: T, index: number, array: T[]) => unknown,
thisArg?: any
): boolean;
which means that when you call marginArray.some(el => el === null)
, all the compiler knows is that this will return a boolean
. It has no idea that this should have any implication whatsoever for the type of marginArray
. So the type of marginArray
is still (number | null)[]
if the call returns true
, and so the compiler balks at
calcAvg(marginArray) // error! marginArray might have nulls in it, I think
If you want to get rid of the error without refactoring, you can use a type assertion. Type assertions are meant for situations like this where you, the developer, know more about the type a value will have than the compiler can figure out. If you are sure that marginArray
will be a number[]
by the time you call calcAvg()
, then you can just tell the compiler that:
calcAvg(marginArray as number[]); // okay
This works, but is not a magic cure-all; by using a type assertion you are taking the responsibility onto yourself for its accuracy. The compiler doesn't know if marginArray
has null
s in it or not; it just believes you when you tell it. If you accidentally lie to the compiler, it won't catch it:
if (marginArray.some(el => el !== null)) { // 😅 wrong check
return 'no data';
return calcAvg(marginArray as number[]); // still no error
So you should double and triple check whenever you make a type assertion that you're doing it right.
On the other hand, if you don't mind some mild refactoring, you can switch from some()
to Array
's every()
method, whose standard library typings includes the following call signature:
interface Array<T> {
every<S extends T>(
predicate: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): this is S[];
This says that if you pass a user-defined type guard function in as predicate
which can narrow the type of the array elements, then every()
can itself be used as a user-defined type guard function which can narrow the type of the whole array. So if predicate
is annotated so that a true
return value implies that value
is number
and not null
, then a true
return value from every()
will tell the compiler that the whole array is a number[]
:
if (!marginArray.every((el: number | null): el is number => el !== null)) {
return 'no data';
return calcAvg(marginArray); // okay
See how the callback function has been annotated to be of type (el: number | null) => el is number
? Now the compiler understands that if calcAvg()
is reached, then marginArray
is a number[]
.
This is probably the closest you can get to type safety. I say "closest" because the act of annotating a function as a user-defined type guard is still the developer telling the compiler something it can't figure out. It would be nice if el => el !== null
would automatically be inferred as a type guard, but it's not. There's at least one open feature request about this: see microsoft/TypeScript#38390. So you can still make the same kind of mistake as above and the compiler won't catch it:
if (!marginArray.every((el: number | null): el is number => el === null)) { // 😅
return 'no data';
At least in the refactored version, though, the scope of potential mistakes is narrower. You have to write el is number
right next to where you write el !== null
, whereas the original version has you writing marinArray as number[]
farther away from where the relevant check is taking place.
Playground link to code
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.