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.

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

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

Cursor Rules for Better AI Development

Finding existing community .cursor/rules for TypeScript lacking, I'm sharing my own set to hopefully kickstart a discussion on what makes effective AI coding guidance. These rules focus purely on TypeScript language features, documentation, structure (like Result types), teaching the AI specific nuances (like noUncheckedIndexedAccess), and practical habits, rather than specific frameworks. I also distinguish between shareable, project-specific Workspace Rules (versioned in Git) and personalized Global Rules (living in your IDE) to tailor the AI to your individual style and workflow. You can download my current ruleset using the link in the original post.

Matt Pocock
Matt Pocock

Should You Declare Return Types?

Here's a quick .cursor/rules addition you can make for handling return types in TypeScript.

# Return Types

When declaring functions on the top-level of a module,
declare their return types. This will help future AI
assistants understand the function's purpose.

```ts
const myFunc = (): string => {
  return "hello";
};
```

One exception to this is components which return JSX.
No need to
Matt Pocock
Matt Pocock

TypeScript Announces Go Rewrite, Achieves 10x Speedup

TypeScript announced a full rewrite of TypeScript in Go. In testing, this rewrite has achieved a 10x speedup in some repositories - and up to 15x in others.

Matt Pocock
Matt Pocock

TypeScript 5.8 Ships --erasableSyntaxOnly To Disable Enums

TypeScript 5.8's new erasableSyntaxOnly flag enforces pure type annotations by disabling enums, namespaces, and parameter properties.

Matt Pocock
Matt Pocock