All Articles

Array<T> vs T[]: Which is better?

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

When you're declaring an array type in TypeScript, you've got one of two options: Array<T> or T[].

Dominik (@TKDodo on Twitter), one of the maintainers of React Query, recently posted an article on which option you should choose.

He strongly advocated for Array<T>, but I think the picture is a little more complex.

Short Explanation

  • Array<T> and T[] are functionally identical in your code.
const firstTest = (arr: Array<string>) => {};

const secondTest = (arr: string[]) => {};

// Both behave the same!
firstTest(["hello", "world"]);
secondTest(["hello", "world"]);
  • Using keyof with T[] can lead to unexpected results.
type Person = {
  id: string;
  name: string;
};

const result: keyof Person[] = ["id", "name"];
Type 'string[]' is not assignable to type 'keyof Person[]'.2322
Type 'string[]' is not assignable to type 'keyof Person[]'.

The fix is to use Array<T> instead:

const result: Array<keyof Person> = ["id", "name"];
  • Dominik argues that Array<string> is more readable than string[]. This is subjective - it's like the difference between reading "array of strings" or "string array".

  • When hovering values or displaying errors, TypeScript uses the T[] syntax. Inexperienced TS devs might experience cognitive load when translating between Array<T> in their code and T[] in their errors.

const array = [1, 2];
const array: number[]
  • Overall, I disagree with Dominik that Array<T> is always the better choice. There are enough caveats to either approach that I won't be making a recommendation one way or the other.

  • But - you should be consistent. You can use this ESLint rule to enforce one or the other in your codebase. And if I had to choose, I would choose T[].

Long Explanation

No Functional Differences

Developers love a syntactical argument - especially when there is little functional difference between the two options.

Array<T> and T[] behave exactly the same, as noted above - with one small exception.

type Test1 = [...Array<string>, ...Array<string>];
A rest element cannot follow another rest element.1265
A rest element cannot follow another rest element. type Test2 = [...string[], ...string[]];
A rest element cannot follow another rest element.1265
A rest element cannot follow another rest element.

Here, we're getting an error using the T[] syntax when trying to use it in a rest position. But even this behavior might disappear in a future TS version, as this PR demonstrates.

So, we can treat the two as functionally the same.

keyof

If you're going to make a firm judgment on which syntax to use, you need to consider the keyof operator.

As described above, keyof with T[] can lead to unexpected results.


type Person = {
  id: string;
  name: string;
};

const result: keyof Person[] = ["id", "name"];
Type 'string[]' is not assignable to type 'keyof Person[]'.2322
Type 'string[]' is not assignable to type 'keyof Person[]'.

You would think here that keyof Person would resolve before the [] operator kicks in, meaning you'd end up with a type like ('id' | 'name')[].

But unfortunately, the [] resolves first, so you end up performing a keyof on Person[].

You can fix this by wrapping keyof Person in parentheses:

const result: (keyof Person)[] = ["id", "name"];

Or, you can use Array<T> instead:

const result: Array<keyof Person> =
const result: (keyof Person)[]
["id", "name"];

Readability

Dominik argues that Array<T> is more readable than T[]. You might agree with this - but I would argue that it's subjective.

I don't want to offer an opinion here - but I want to make sure your opinion is well-informed.

Readonly Arrays

If you want to stay consistent with Array<T>, you'll probably also want to use the ReadonlyArray<T> type:


const array: ReadonlyArray<string> = ["hello", "world"];

array.push("foo");
Property 'push' does not exist on type 'readonly string[]'.2339
Property 'push' does not exist on type 'readonly string[]'.

We can compare this to the readonly T[] syntax:

const array2: readonly string[] = ["hello", "world"];

array2.push("foo");
Property 'push' does not exist on type 'readonly string[]'.2339
Property 'push' does not exist on type 'readonly string[]'.

Which do you prefer? I find this one pretty hard to differentiate.

Arrays of Arrays

For handling arrays of arrays, you'll also want to consider Array<Array<T>>:


const array: Array<Array<string>> = [
  ["hello", "world"],
  ["foo", "bar"],
];

We can compare it to the T[][] syntax:


const array2: string[][] = [
  ["hello", "world"],
  ["foo", "bar"],
];

Which do you prefer?

TypeScript Uses T[]

TypeScript does offer an opinion on which it prefers. In hovers and errors, TypeScript will always use the T[] syntax.

const array = [1, 2];
const array: number[]
const asConstArray = [1, 2] as const;
const asConstArray: readonly [1, 2]
const arrayOfArrays = [
const arrayOfArrays: number[][]
[1, 2], [3, 4], ]; const stringArray = ["hello", "world"]; const numArray: number[] = stringArray;
Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.2322
Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.

This means that if you're using Array<T> in your code, less experienced TypeScript developers will experience some cognitive load translating between the two syntaxes.

This is a big reason why, to me at least, T[] feels more natural - it's more present in the language, encouraged by the compiler, and used everywhere in the docs.

Conclusion

After all of this deep thought, I don't think there's a clear winner here.

I have personally lost hours of my life trying to figure out why keyof T[] wasn't working as expected.

But I can also see a strong argument that T[] is the more intuitive choice because of how embedded it is within TypeScript as a whole.

It really comes down to one question: would I reject a PR containing the one we didn't use? No.

Use whichever you like. Use a linter to stay consistent. And don't worry about it too much.

Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock