The reason for this behavior stems from the difference in how the array length is perceived. In one scenario, the array length is definite, while in the other it remains uncertain. If the array length could potentially be zero, then the output of undefined
becomes a valid possibility.
For instance, when you initialize an array like const arr = [1, 2, 3]
, the type assigned to arr
is number[]
. Essentially, this signifies that it is an array of numbers with the flexibility to expand or contract. Although it may seem obvious that the array won't shrink to a zero-length before the subsequent line, this information is not reflected in the type. Consequently, when the array is passed to a function like sample, the type inference allows for the potential of undefined as an output.
On the other hand, when the array is created and used within the same instance, such as _.sample([1, 2, 3]);
, TypeScript can deduce a more specific type due to the direct usage. In this case, the inferred type is [number, number, number]
, indicating a tuple of length 3, ensuring that an element will always be returned.
If you wish to enforce a stricter type when the array declaration is on a separate line, you can explicitly specify this to TypeScript. One approach is to use as const
, signaling that the array will remain unchanged:
const arr = [1, 2, 3] as const; // readonly [number, number, number]
const myNumber = _.sample(arr); // number
The type definitions for the function sample
can be found here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/common/collection.d.ts#L1641. The handling of tuples is specified by this type:
sample<T>(collection: readonly [T, ...T[]]): T;
Whereas, the treatment of arrays is defined by this type:
sample<T>(collection: Dictionary<T> | NumericDictionary<T> | null | undefined): T | undefined;