Skip to content

Array

The array category covers types that work on Array<T> and, in most cases, on tuples too. Types that only make sense on a fixed-length tuple live in tuple.

Many of these types are also exported under the ArrayPlus namespace, which holds the array-specific variant when a tuple-specific one also exists.

Sources: packages/type-plus/src/array.

type IsArray<T, $O extends IsArray.$Options = {}>
type IsNotArray<T, $O extends IsNotArray.$Options = {}>
type R = IsArray<number[]> // true
type R = IsArray<[1]> // true
type R = IsArray<number> // false
type R = IsNotArray<number> // true

The check is loose: a tuple is an array, the same way a string literal is a string. Pass exact to narrow the check to Array<T> only:

type R = IsArray<[1], { exact: true }> // false
type R = IsArray<number[], { exact: true }> // true

Both accept type branching options:

type R = IsArray<number[], { selection: 'filter' }> // number[]
type R = IsArray<number, { selection: 'filter' }> // never
type R = IsArray<number[] | 1, { distributive: false }> // false
type Head<T extends readonly unknown[], Options extends Head.Options = Head.DefaultOptions>
type Last<T extends readonly unknown[], Options extends Last.Options = Last.DefaultOptions>
type R = Head<[1, 2, 3]> // 1
type R = Last<[1, 2, 3]> // 3
type R = Head<string[]> // string
type R = Last<[]> // never

Both take Options['$never'] and Options['caseEmptyTuple'] to override the never and [] cases.

type At<A extends readonly unknown[], N extends number, Fail = never>
type IndexAt<A extends readonly unknown[], N extends number, Options extends IndexAt.Options = IndexAt.DefaultOptions<A, N>>
type IsIndexOutOfBound<A extends readonly unknown[], N extends number, $O extends IsIndexOutOfBound.$Options = {}>

At reads the element type at index N, and like Array.at() supports negative numbers. IndexAt normalizes the index itself.

type R = At<[1, 2, 3], 2> // 3
type R = At<[1, 2, 3], -1> // 3
type R = IndexAt<['a', 'b', 'c'], -2> // 1
type R = IndexAt<['a', 'b', 'c'], 3> // 3 (upper bound)
type R = IsIndexOutOfBound<[1], 1> // true
type R = IsIndexOutOfBound<[1], 1, { selection: 'filter' }> // 1
type R = IsIndexOutOfBound<[1], 0, { $then: 'yes'; $else: 'no' }> // 'no'

IsIndexOutOfBound accepts the full type branching options. Before 8.0.0 it took Then and Else positionally; move them into { $then, $else }.

IndexAt takes an options object to override each case it can land on: Options['$never'] when A is never (default never), Options['$array'] when A is an array rather than a tuple (default N), Options['caseEmptyTuple'] when A is [] (default never), Options['caseUpperBound'] when N is past the upper bound (default A['length']), and Options['caseLowerBound'] when N is past the lower bound (default 0).

type R = IndexAt<never, 0, { $never: 'n' }> // 'n'
type R = IndexAt<string[], 0, { $array: 'a' }> // 'a'
type R = IndexAt<[], 0, { caseEmptyTuple: 'e' }> // 'e'
type R = IndexAt<[1], 1, { caseUpperBound: 'u' }> // 'u'
type R = IndexAt<[1], -2, { caseLowerBound: 'l' }> // 'l'

Before v8 these cases were positional type parameters (IndexAt<A, N, Fail, Upper, Lower>); move them into the options object.

type Filter<A extends readonly unknown[], Criteria>
type KeepMatch<A extends readonly unknown[], Criteria> // alias of Filter
type ArrayPlus.DropMatch<A extends Readonly<unknown[]>, Criteria>

Filter keeps the entries satisfying Criteria. DropMatch removes them.

type R = Filter<[1, 2, '3'], number> // [1, 2]
type R = Filter<Array<string | undefined>, string> // string[]
type R = ArrayPlus.DropMatch<Array<string | undefined>, undefined> // string[]
type R = ArrayPlus.DropMatch<Array<string>, string> // never[]

ArrayPlus.Filter is the array-only variant, with Options['$never'] and Options['$notArray'].

type FindFirst<A, Criteria, Options extends FindFirst.Options = ...>
type FindLast<A extends readonly unknown[], Criteria>
type ArrayPlus.Find<A, Criteria, Options extends Find.Options = ...>
type Some<A extends readonly unknown[], Criteria, Mode extends 'strict' | 'loose' = 'loose', Then = true, Else = false>
type R = FindFirst<[true, 1, 'x', 3], string> // 'x'
type R = FindLast<[true, 123, 'x', 321], number> // 321
type R = FindFirst<Array<string>, string> // string
type R = FindFirst<[true, 1, 'x'], 2> // never
type R = Some<['a', true], boolean> // true
type R = Some<['a', true], boolean, 'strict'> // false

FindFirst and ArrayPlus.Find match widened types by default: FindFirst<Array<number>, 1> is 1 | undefined. Set Options['widen'] to false, or Options['$widen'] to never, for a purely type-centric result. ElementMatch<T, Criteria, Options> is the single-element matcher these are built on.

💀 deprecated: Concat — use ArrayPlus.Concat instead.

type Reverse<A extends unknown[]>
type Concat<A extends Readonly<unknown[]>, B extends Readonly<unknown[]>>
type PadStart<A extends readonly unknown[], MaxLength extends number, PadWith = unknown>
type ArrayPlus.SplitAt<A, Index extends number, DeleteCount extends number = never, Insert extends readonly unknown[] = never>
type R = Reverse<[1, 2, 3]> // [3, 2, 1]
type R = Concat<[1], [2, 3]> // [1, 2, 3]
type R = PadStart<[1, 2, 3], 5, 0> // [0, 0, 1, 2, 3]
type R = PadStart<[1, 2, 3], 5> // [unknown, unknown, 1, 2, 3]
type R = PadStart<number[], 1, string> // [string, ...number[]]
type R = ArrayPlus.SplitAt<[1, 2, 3, 4, 5], 2> // [[1, 2], [3, 4, 5]]
type R = ArrayPlus.SplitAt<[1, 2, 3, 4, 5], 2, 2, ['a', 'b']> // [[1, 2, 'a', 'b', 5], [3, 4]]

SplitAt accepts negative indexes and clamps an out-of-bound index to the boundary.

🗑️ removed in 8.0.0: use FindFirst and PadStart instead.

Both were thin aliases kept for the v7 line. Neither was exported from the package entry point in v7, so this only affects deep imports:

Removed Replacement
First<A, Criteria> FindFirst<A, Criteria>, ArrayPlus.Find<A, Criteria>
PadLeft<A, Total, PadWith> PadStart<A, MaxLength, PadWith>

Concat is deprecated rather than removed — use ArrayPlus.Concat.

type UnionOfValues<A extends readonly unknown[]>
type UnionOfProps<A extends readonly Record<any, any>[], P extends KeyTypes>
type IntersectOfProps<A extends readonly Record<any, unknown>[], P extends KeyTypes>
type ArrayPlus.CommonPropKeys<A extends readonly Record<KeyTypes, unknown>[], Options = ...>
type R = UnionOfValues<[1, 2, 3]> // 1 | 2 | 3
type R = UnionOfProps<[{ a: 1 }, { a: 2 }], 'a'> // 1 | 2
type R = IntersectOfProps<[{ a: { x: 1 } }, { a: { y: 2 } }], 'a'> // { x: 1 } & { y: 2 }
type R = ArrayPlus.CommonPropKeys<Array<{ a: 1; b: 1 } | { a: 1; c: 1 }>> // 'a'

ArrayValue, PropUnion and MapToProp are older names for UnionOfValues, UnionOfProps and IntersectOfProps.

type ArrayPlus.Entries<A extends readonly unknown[]>
type ArrayPlus.IsReadonly<A, $Options extends IsReadonly.Options = IsReadonly.DefaultOptions>
type R = ArrayPlus.Entries<[1, 2, 3]> // [[0, 1], [1, 2], [2, 3]]
type R = ArrayPlus.Entries<Array<string | number>> // Array<[number, string | number]>
type R = ArrayPlus.IsReadonly<readonly [1, 2]> // true
type R = ArrayPlus.IsReadonly<[1, 2]> // false

IsReadonly takes $then, $else, $never and $notArray branches.

🗑️ removed in 8.0.0: use IsArray and IsNotArray instead.

LooseArrayType and its variances were a stopgap while ArrayType still did a strict, tuple-excluding check. IsArray is loose by default, so the stopgap is no longer needed:

Removed Replacement
LooseArrayType<T> IsArray<T, { selection: 'filter' }>
IsLooseArray<T> IsArray<T>
NotLooseArrayType<T> IsNotArray<T, { selection: 'filter' }>
IsNotLooseArray<T> IsNotArray<T>

IsArray also distributes over unions, so the filter form returns only the array members:

type R = IsArray<number[] | 1, { selection: 'filter' }> // number[]
Function Description
literalArray(...entries) Returns an array whose items are restricted to the provided literals.
reduceWhile(predicate, callbackfn, initialValue, array) reduce() with a predicate for early termination.