107

I have a list of numbers that I know is never empty. Is it possible to define an array in Typescript that is never empty?

I know that it is possible with tuples like [ number, number ] but this will not work as my array can be any size.

I guess what I am looking for is a NonEmptyArray<number> type.

Does it exist? :)

4
  • do you mean like items required? Commented May 6, 2019 at 13:22
  • github.com/Microsoft/TypeScript/issues/10272 Commented May 6, 2019 at 13:23
  • It seems like one should be able to use Exclude for this, but although type NonEmptyNumberArray = Exclude<number[], []>; parses, the result is just an alias for number[]. Commented May 6, 2019 at 13:38
  • I mean like type MyArray = [ number, number ]. Just for n items instead of only two. Commented May 6, 2019 at 13:40

4 Answers 4

169

A feature request for allowing you to just check array.length > 0 to guard against empty arrays, microsoft/TypeScript#38000, was declined as being too complex. Essentially you cannot usually simply check length in TypeScript to convince the compiler about the availability of properties at given numeric keys.

You can define a non-empty array type like this:

type NonEmptyArray<T> = [T, ...T[]];

const okay: NonEmptyArray<number> = [1, 2];
const alsoOkay: NonEmptyArray<number> = [1];
const err: NonEmptyArray<number> = []; // error!

This is due to support added in TS 3.0 for rest elements in tuple types. I'm not sure what your use case is... It's probably more annoying to use that type than you expect, though:

function needNonEmpty(arr: NonEmptyArray<number>) {}
function needEmpty(arr: []) {}

declare const bar: number[];
needNonEmpty(bar); // error, as expected

if (bar.length > 0) {
    needNonEmpty(bar); // ugh, still error!
}

If you want a length check to work, you'll need to use something like a user-defined type guard function, but it's still annoying to use:

function isNonEmptyArray<T>(arr: T[]): arr is NonEmptyArray<T> {
    return arr.length > 0;
}

if (isNonEmptyArray(bar)) {
    needNonEmpty(bar); // okay
} else {
    needEmpty(bar); // error!! urgh, do you care?        
}
Sign up to request clarification or add additional context in comments.

7 Comments

Yeah, basically use a type guard before you call it. Must be what the OP wants so it can be enforced by the compiler. Really cool and weird, I hope I never need this :)
My use case is this: when using functions like head() and last() on a list of T they as default return T | undefined. Which means that I have to make a check or in cases when I know that the array is never empty I have to typecast. If I instead have a NonEmptyArray<T> my head and last functions could return T always and neither checks nor typecasting will be necessary.
That's great but you will still probably have to make those checks/assertions before you call head() and last() instead of during or after.
Even after using these type guards, the Array<T> return types for pop() or shift() doesn't change, so you either have to cast the result anyway, or use your own functions as in the example above. Any built in array methods wouldn't get better return types inferred.
Doesn't work as a value of an object when passed through a variable/function. E.g. const foo = {a: [1]}; const bar: { [key: string]: NonEmptyArray<any> } = foo yields error Type 'number[]' is not assignable to type 'NonEmptyArray<any>'.
|
48

I've also wondered about this and came up with a different solution:

type NonEmptyArray<T> = T[] & { 0: T };

6 Comments

A further alternative in this style is type NonEmpty<T> = T extends Array<infer U> ? U[] & {'0': U} : never;. Usage: const x: NonEmpty<string[]> = ["myString"];. This separates the NonEmpty part from the Array part.
This doesn't cover arrays where the first member is undefined, but others have values.
@jsejcksn Can you give an example?
I see arr as having the type NonEmptyArray<number | undefined> in that case. Depends on how you define the type you actually want to use.
This is essentially the definition used by fp-ts.
|
16

Note for a happy reader. None of the above solutions is viable.

type NonEmptyArray<T> = T[] & { 0: T }
// type NonEmptyArray<T> = [T, ...T[]] -- same behavior

// ISSUE 1: map does not preserve Non-Emptiness
const ns: NonEmptyArray = [1]
const ns2 = ns.map(n => n) // number[] !!!

// ISSUE 2: length check does not make an array Non-Empty
function expectNonEmpty<T>(ts: NonEmptyArray<T>): any {}

if (ns2.length > 0) {
  expectNonEmpty(ns2) // type error
}

Just FYI, so you know why it's unlikely to see NonEmptyArray in practice. The above replies should have mention this.

6 Comments

This should be a comment, not an answer.
Keep thinking. No way to format that code in a comment.
Concerning the length check issue you can just use a check function that will type the array using the is keyword: function isNonEmpty<T>(array: T[]): array is NonEmptyArray<T> {return array.length > 0}. It will make your codebase more verbose but also more type safe ;)
Yes, it's doable. But if you always have to keep in mind something (don't use lenght, use ad-hoc map replacements, etc.) – it's not really that type-safe. More pain than gain, does not worth it.
@IvanKleshnin We can argue on the "more pain than gain" but it's definitely more type safe.
|
13

Although type [T, ...T[]] cover most cases, it won't accept arrays like [...anotherArray, 'element']. I personally use type as presented below, to cover wider spectrum of cases:

export type NonEmptyArray<T> = [T, ...T[]] | [...T[], T] | [T, ...T[], T];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.