82

How can you create a type in TypeScript that only accepts arrays with two or more elements?

needsTwoOrMore(["onlyOne"]) // should have error
needsTwoOrMore(["one", "two"]) // should be allowed
needsTwoOrMore(["one", "two", "three"]) // should also be allowed

7 Answers 7

114

Update 2021: shorter notation:

type arrMin1Str = [string, ...string[]]; // The minimum is 1 string.

OR

type arrMin2Strs = [string, string, ...string[]]; // The minimum is 2 strings.

OR

type arrMin3Strs = [string, string, string, ...string[]]; // The minimum is 3 strings.

OR …so on

This is just an addition to @KPD and @Steve Adams answers, since all specified types are the same. This should be a valid syntax since TypeScript 3.0+ (2018).

Sign up to request clarification or add additional context in comments.

3 Comments

Not the prettiest, but works like a charm!
causes TS2556 if a function is called like foo(...myArray)
Seems to have some other limitations too. For instance [string, ...string[]] is not regarded as equivalent to [...string[], string]. If you have x: string[], then [...x, 'a'] will not match arrMin1Str.
68

This can be accomplished with a type like:

type ArrayTwoOrMore<T> = {
    0: T
    1: T
} & Array<T>

declare function needsTwoOrMore(arg: ArrayTwoOrMore<string>): void

needsTwoOrMore(["onlyOne"]) // has error
needsTwoOrMore(["one", "two"]) // allowed
needsTwoOrMore(["one", "two", "three"]) // also allowed

TypeScript Playground Link

4 Comments

How do you do this with interface? say items: Item[] = [];, I need to require atleast 1 Item
@ColdCerberus There is no difference. The Item[] syntax is just sugar for Array<Item>. So just like the example in the answer had ArrayTwoOrMore<string> you can have ArrayTwoOrMore<Item>. To change it to ArrayOneOrMore, just copy the type ArrayTwoOrMore definition and remove the 1: T line.
Although this approach works for array declarations, when you are transforming a non-empty array into something else using Array#map, TypeScript will not understand that the output has a 0 key: Property '0' is missing in type 'MyType[]' but required in type '{ 0: MyType; }'.
This doesn't work anymore. TS now interprets this type to mean that the array has one and only one element. stackoverflow.com/a/67026991/5091709 works.
39

This is an old question and the answer is fine (it helped me as well), but I just stumbled across this solution as well while playing.

I had already defined a typed Tuple (type Tuple<T> = [T, T];), and then below that, I had defined array of two or more as described above (type ArrayOfTwoOrMore<T> = { 0: T, 1: T } & T[];).

It occurred to me to try using the Tuple<T> structure in place of { 0: T, 1: T }, like so:

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

And it worked. Nice! It's a little more concise and perhaps able to be clearer in some use-cases.

Worth noting is that a tuple doesn't have to be two items of the same type. Something like ['hello', 2] is a valid tuple. In my little code snippet, it just happens to be a suitable name and it needs to contain two elements of the same type.

Comments

22

Expanding on Oleg's answer you can also create a type for arrays of arbitrary minimum length:

type BuildArrayMinLength<
  T,
  N extends number,
  Current extends T[]
> = Current['length'] extends N
  ? [...Current, ...T[]]
  : BuildArrayMinLength<T, N, [...Current, T]>;

type ArrayMinLength<T, N extends number> = BuildArrayMinLength<T, N, []>;

const bad: ArrayMinLength<number, 2> = [1]; // Type '[number]' is not assignable to type '[number, number, ...number[]]'.
const good: ArrayMinLength<number, 2> = [1, 2];

Based on Crazy, Powerful TypeScript 4.1 Features.

1 Comment

This is really cool - I didn't realize TS can infer what the length would be like that. It even knows when you're casting incorrectly - typing [1] as ArrayMinLength<string, 4> gives a very clear and correct error.
8
type FixedTwoArray<T> = [T,T]
interface TwoOrMoreArray<T> extends Array<T> {
    0: T
    1: T
}

let x: FixedTwoArray<number> = [1,2];
let y: TwoOrMoreArray<string> = ['a','b','c'];

TypeScript Playground

Comments

6

By building on the excellent work in the other answers here, I ended up with the following custom generic that I feel reads a little more cleanly and can provide array types of both exact and minimum lengths.

type BuildArrayOf<
    Quantifier extends 'exactly' | 'at least',
    Count extends number,
    Type,
    Current extends Type[]
    > = Current['length'] extends Count
    ? Quantifier extends 'exactly'
    ? [...Current]
    : [...Current, ...Type[]]
    : BuildArrayOf<Quantifier, Count, Type, [...Current, Type]>;

/** An array of a given type comprised of either exactly or at least a certain count of that type. */
type ArrayOf<Quantifier extends 'exactly' | 'at least', Count extends number, Type> = BuildArrayOf<
    Quantifier,
    Count,
    Type,
    []
>;

declare function needsExactlyTwo(strings: ArrayOf<'exactly', 2, string>): void

needsExactlyTwo(["onlyOne"]) // error
needsExactlyTwo(["one", "two"]) // valid
needsExactlyTwo(["one", "two", "three"]) // error

declare function needsAtLeastThree(strings: ArrayOf<'at least', 3, string>): void

needsAtLeastThree(["onlyOne"]) // error
needsAtLeastThree(["one", "two"]) // error
needsAtLeastThree(["one", "two", "three"]) // valid
needsAtLeastThree(["one", "two", "three", "four"]) // valid

You can also check it out in the TypeScript Playground.

Comments

1

This typescript definition file can limit the size of array by both minimum and maximum boundaries. Copy the whole thing into a single file and the exported type ArrayOf is used to accomplish the desired behavior.


type MaxArrayLength<Type, Count extends number> = BuildArrayOf<
  "exactly",
  Count,
  Type,
  []
> extends [Type, ...infer Rest]
  ? [Type, ...Rest] | MaxArrayLength<Type, Rest["length"]>
  : Count extends 1
  ? [] | [Type]
  : [];

type BuildArrayOf<
  Quantifier extends "exactly" | "min" | "max",
  Count extends number,
  Type,
  Current extends Type[]
> = Quantifier extends "max"
  ? MaxArrayLength<string, Count>
  : Current["length"] extends Count
  ? Quantifier extends "exactly"
    ? [...Current]
    : [...Current, ...Type[]]
  : BuildArrayOf<Quantifier, Count, Type, [...Current, Type]>;

type NonNegativeInteger<T extends number> = number extends T
  ? never
  : `${T}` extends `-${string}` | `${string}.${string}`
  ? never
  : T;

export type ArrayOf<
  Type,
  MinLength extends number | undefined = undefined,
  MaxLength extends number | undefined = undefined
> = MinLength extends number
  ? NonNegativeInteger<MinLength> extends never
    ? never
    : MaxLength extends number
    ? NonNegativeInteger<MaxLength> extends never
      ? never
      : MinLength extends MaxLength
      ? BuildArrayOf<"exactly", MinLength, Type, []>
      : BuildArrayOf<"exactly", MinLength, Type, []> extends [
          Type,
          ...infer Rest
        ]
      ? Exclude<
          BuildArrayOf<"max", MaxLength, Type, []>,
          BuildArrayOf<"max", Rest["length"], Type, []>
        >
      : MinLength extends 1
      ? Exclude<BuildArrayOf<"max", MaxLength, Type, []>, []>
      : BuildArrayOf<"max", MaxLength, Type, []>
    : BuildArrayOf<"min", MinLength, Type, []>
  : MaxLength extends number
  ? NonNegativeInteger<MaxLength> extends never
    ? never
    : BuildArrayOf<"max", MaxLength, Type, []>
  : Type[];

The usage is simple. Take a look:


const arrayWithNoBoundary: ArrayOf<string> = []; // equals to string[]

const arrayWithMinimumLength: ArrayOf<string, 2> = ["", ""] // equals to [string, string, ...string[]]

const arrayWithMaximumLength: ArrayOf<string, undefined, 2> = []; // equals to ([] | [string] | [string, string])

const arrayWithBothLimits: ArrayOf<string, 3, 5> = ["", "", "", ""] // equals to ([string, string, string] | [string, string, string, string] | [string, string, string, string, string])

const arrayOfFixedSie: ArrayOf<string, 3, 3> = ["", "", ""] // equals to [string, string, string]

Invalid usages will turn the type into never. Here is a list of all invalid usages:

type Invalid = ArrayOf<SomeType, number, number> // Min and max must be of type either undefined or an exact positive integer. The 'number' type is not accepted

type Invalid = ArrayOf<SomeType, -1, 1.2> // Negative numbers or numbers with decimals are invalid for both min and max length.

type Invalid = ArrayOf<SomeType, 2, 1> // Min length can not be grater than max length

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.