A TypeScript array is an ordered list of values stored in one variable, where each value has a numeric index starting at 0. It is the regular JavaScript Array with a type attached, so a string[] accepts only strings and pushing a number into it is a compile error. Arrays grow and shrink at runtime, which is why TypeScript has no separate List type. When Java developers look for a TypeScript list, the array is the answer.
We use an array for any ordered list of data, such as the items in a shopping cart or the rows that a REST API returns for a table.
The following example covers each task in this guide in one snippet, written for TypeScript 7.0 with strict on and Node.js 22. The comments show the value each line produces.
// 1. Create typed arrays
const fruits: string[] = ["apple", "banana", "cherry"];
const scores: Array<number> = [10, 20, 30];
// 2. Read elements
const first = fruits[0]; // first = "apple"
const last = fruits.at(-1); // last = "cherry"
const count = fruits.length; // count = 3
// 3. Add and remove at the end
fruits.push("mango"); // ["apple", "banana", "cherry", "mango"]
const removed = fruits.pop(); // removed = "mango"
// 4. Copy with a change (ES2023)
const sorted = scores.toSorted((a, b) => b - a); // sorted = [30, 20, 10]
const updated = scores.with(0, 15); // updated = [15, 20, 30]
// 5. Filter and find
const big = scores.filter((s) => s > 15); // big = [20, 30]
const found = scores.find((s) => s > 15); // found = 20
// 6. Loop
for (const fruit of fruits) {
console.log(fruit); // apple, banana, cherry
}
// 7. Merge into a new array
const all = [...fruits, "grape"]; // all = ["apple", "banana", "cherry", "grape"]
// 8. Read-only array and tuple
const days: readonly string[] = ["Mon", "Tue"];
const person: [string, number] = ["Lokesh", 37];
Notice that toSorted() and with() in step 4 return new arrays, so scores keeps its original values.
In the sections that follow, we declare and type arrays, read elements safely, loop over and copy them, and lock them with the readonly modifier. We also cover tuples and the ES2023 methods (toSorted(), toReversed(), toSpliced() and with()) that return a changed copy instead of modifying the original array. Adding, removing, slicing, filtering and searching each have a detailed article, linked from section 3.
1. Declaring a Typed Array
TypeScript needs to know the element type of every array. We either write the type ourselves or let the compiler infer it from the initial values. Once the type is fixed, every later push(), assignment or method call is checked against it.
1.1. string[] and Array<string>
There are two ways to write an array type, and both mean the same thing.
- The short form is string[].
- The generic form is Array<string>, where the element type goes between angle brackets.
Most code and the TypeScript Handbook use the short form; the generic form reads better when the element type is long.
// 1. Two ways to write the type
const fruits: string[] = ["apple", "banana"];
const prices: Array<number> = [5, 3];
// 2. Type inferred from the values
const ages = [37, 35, 40]; // number[]
// 3. Empty array: write the type
const names: string[] = [];
names.push("Lokesh"); // names = ["Lokesh"]
For an empty array, we always write the type. Without it, the compiler has nothing to infer from and has to guess the type from later code, so the variable type is not visible where we declare it.
1.2. Arrays With a Fixed Starting Length
Sometimes we need an array of a known size before we have the values, for example a row of counters. The Array constructor with one number creates that many empty slots, not elements with a value. The method fill() puts the same value in every slot, and Array.from() builds each element with a function that receives the index.
const slots = new Array<number>(3); // [ <3 empty items> ]
const zeros = new Array<number>(3).fill(0); // zeros = [0, 0, 0]
const evens = Array.from({ length: 3 }, (_, i) => i * 2); // evens = [0, 2, 4]
The methods forEach() and map() skip empty slots, so we fill the array before using it.
1.3. Mixed Values, Objects and Nested Arrays
An array can hold more than one type. We write a union type (a type that accepts any of several types) inside parentheses, as in (string | number)[]. The parentheses matter. Without them, string | number[] means “a single string, or an array of numbers”, and the compiler rejects an array that contains a string.
// 1. Mixed values: parentheses around the union
const values: (string | number)[] = ["apple", 5];
// 2. Objects
interface Person { name: string; age: number; }
const people: Person[] = [
{ name: "Lokesh", age: 37 },
{ name: "Raj", age: 35 },
];
const raj = people[1].name; // raj = "Raj"
// 3. Nested arrays (a grid)
const grid: number[][] = [
[1, 2],
[3, 4],
];
const cell = grid[1][0]; // cell = 3
For the version without parentheses, TypeScript 7.0 reports error TS2322.
src/err.ts(1,7): error TS2322: Type '(string | number)[]' is not assignable to type 'string | number[]'.
Type '(string | number)[]' is not assignable to type 'number[]'.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
Arrays of objects come up in almost every app, for example a list of orders loaded from a REST API. We often sort and group arrays of objects by a property, such as orders by date.
2. Reading Elements by Index
Indexes start at 0, so fruits[0] is the first element and fruits[fruits.length – 1] is the last. The at() method also accepts negative numbers, which count from the end, so at(-1) returns the last element without the length arithmetic.
const fruits = ["apple", "banana", "cherry"];
// 1. By index (zero-based)
const first = fruits[0]; // first = "apple"
const outside = fruits[5]; // outside = undefined, typed string
// 2. From the end with at()
const last = fruits.at(-1); // last = "cherry", typed string | undefined
// 3. Length
const count = fruits.length; // count = 3
// 4. Destructuring
const [a, b] = fruits; // a = "apple", b = "banana"
The outside line shows a gap in the type checking. Reading past the end returns undefined at runtime, but TypeScript still types fruits[5] as string, so a later outside.toUpperCase() compiles and then throws a TypeError. For example, a page that shows the third search result throws that TypeError when the search returns only two results.
The at() method returns the more accurate type string | undefined. To get the same check for the bracket syntax, we turn on the noUncheckedIndexedAccess option in tsconfig.json. The option is not part of strict, because it also adds undefined to indexes that we know are valid.
3. Adding, Removing, Slicing and Searching
These everyday tasks each have a dedicated article with every variant and its pitfalls. For each task, the table names the main methods, and the last column says whether the method changes the original array. That column matters in shared state such as React state or a function argument, where changing the caller’s array causes bugs that are hard to trace.
| Task | Methods | Changes the original? | Detailed article |
|---|---|---|---|
| Add elements | push(), unshift(), splice(), spread, concat() | push(), unshift(), splice(): yes | Add or append items |
| Remove elements | pop(), shift(), splice(), filter(), toSpliced() | pop(), shift(), splice(): yes | Remove an item |
| Take a part of an array | slice() | No | Array slice() |
| Keep elements that match | filter() | No | Array filter() |
| Find one element or its index | find(), findIndex(), findLast(), findLastIndex() | No | Array find() |
| Transform every element | map(), flatMap() | No | map() vs flatMap() |
Two more methods answer yes-or-no questions.
- The method includes(value) checks whether a value is in the array.
- The method some(fn) checks whether at least one element passes a test.
Both return a boolean and are cheaper to read than a find() followed by a check.
4. Changing a Copy With ES2023 Methods
The older methods sort(), reverse() and splice() modify the array they are called on. ES2023 added a copying version of each one, plus with() for replacing a single element. These methods return a new array and leave the original untouched, so we no longer need to write […scores].sort(). For example, a leaderboard component that sorts the scores it gets from its parent calls toSorted(), because sort() would reorder the parent’s array as well.
const scores = [30, 10, 20];
// 1. Sort a copy
const sorted = scores.toSorted((a, b) => a - b); // sorted = [10, 20, 30]
// 2. Reverse a copy
const reversed = scores.toReversed(); // reversed = [20, 10, 30]
// 3. Remove or insert in a copy
const spliced = scores.toSpliced(1, 1); // spliced = [30, 20]
// 4. Replace one element in a copy
const changed = scores.with(0, 99); // changed = [99, 10, 20]
// scores is unchanged: [30, 10, 20]
Each copying method has the same parameters as its older partner, which makes switching low-risk. The method with(index, value) also accepts a negative index such as -1 for the last element, and it throws a RangeError when the index is outside the array, while a plain assignment creates empty slots without an error.
| Changes the original | Returns a new array (ES2023) |
|---|---|
| sort(compareFn) | toSorted(compareFn) |
| reverse() | toReversed() |
| splice(start, deleteCount, …items) | toSpliced(start, deleteCount, …items) |
| arr[i] = value | with(i, value) |
These methods work in Node.js 20 and later and in all current browsers. In TypeScript, the lib (or target) option in tsconfig.json must be ES2023 or later. With an older lib, the compiler reports error TS2550.
src/err.ts(2,23): error TS2550: Property 'toSorted' does not exist on type 'number[]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2023' or later.
One trap applies to both sort() and toSorted(). Without a compare function, they convert the elements to strings and compare them as text, so numbers end up in the wrong order. For numbers, we always pass (a, b) => a – b.
const nums = [10, 9, 1];
const wrong = nums.toSorted(); // wrong = [1, 10, 9]
const right = nums.toSorted((a, b) => a - b); // right = [1, 9, 10]
ES2023 also added findLast() and findLastIndex(), which search from the end of the array and otherwise work like find() and findIndex().
5. Looping Over an Array
For most loops, for…of is the best choice. It gives us each value with its element type and works with break and continue. When we also need the index, entries() returns [index, value] pairs that we split with destructuring.
const fruits = ["apple", "banana"];
// 1. Values
for (const fruit of fruits) {
console.log(fruit); // apple, banana
}
// 2. Index and value
for (const [i, fruit] of fruits.entries()) {
console.log(i, fruit); // 0 apple, 1 banana
}
// 3. forEach
fruits.forEach((fruit, i) => console.log(i, fruit)); // 0 apple, 1 banana
// 4. for...in gives string keys, not values
for (const key in fruits) {
console.log(typeof key, key); // string 0, string 1
}
The forEach() method cannot be stopped early with break, and an await inside its callback does not pause the loop. For example, saving each cart item with await inside forEach() starts all the requests at once. The for…in loop is meant for object properties. On an array, it returns the indexes as strings, so we avoid it. The classic for loop with a counter, for (let i = 0; i < fruits.length; i++), still works when we need to step by two or walk backwards.
6. Copying and Merging Arrays
Assigning an array to another variable copies only the reference, so both variables point to the same array. To get a separate array, we use the spread operator (…), which also merges several arrays into one. The method concat() does the same merge and is useful when the arrays come from method calls.
const fruits = ["apple", "banana"];
const more = ["cherry"];
// 1. Shallow copy
const copy = [...fruits]; // copy = ["apple", "banana"]
copy.push("mango"); // fruits stays ["apple", "banana"]
// 2. Merge
const merged = [...fruits, ...more]; // merged = ["apple", "banana", "cherry"]
const joined = fruits.concat(more); // joined = ["apple", "banana", "cherry"]
The spread copy is shallow. It creates a new array, but objects inside it are shared with the original. Changing a property through the copy changes it in the original as well. For a deep copy, structuredClone() copies the array and every object in it. It is available as a global function in Node.js 17 and later and in all current browsers.
const people = [{ name: "Lokesh", age: 37 }];
const shallow = [...people];
shallow[0].age = 38; // people[0].age = 38 too
const deep = structuredClone(people);
deep[0].age = 40; // people[0].age stays 38
A deep copy costs more time and memory, and structuredClone() cannot copy functions or class methods. We use structuredClone() only when the copy needs independent nested objects, such as an edit form that must not change the original order until the user clicks Save.
7. Read-Only Arrays
A readonly array type removes every method that changes the array, such as push(), pop(), splice() and sort(), and it blocks assignment by index. We use it for function parameters and shared constants, so the compiler tells us when some code tries to change data it does not own. There are three ways to get a read-only array.
// 1. Two ways to write a read-only array type
const days: readonly string[] = ["Mon", "Tue"];
const months: ReadonlyArray<string> = ["Jan", "Feb"];
// 2. as const: read-only tuple of literal types
const sizes = ["S", "M", "L"] as const; // readonly ["S", "M", "L"]
// 3. Copying methods still work
const sortedDays = days.toSorted(); // sortedDays = ["Mon", "Tue"]
const withWed = [...days, "Wed"]; // withWed = ["Mon", "Tue", "Wed"]
The as const assertion goes further than readonly. It also keeps the exact values as literal types, so sizes can only ever contain “S”, “M” and “L”. With typeof sizes[number], we turn the sizes array into a union of literal types. Any attempt to change a read-only array fails at compile time.
const days: readonly string[] = ["Mon", "Tue"];
days.push("Wed");
days[0] = "Sun";
days.sort();
src/err.ts(3,6): error TS2339: Property 'push' does not exist on type 'readonly string[]'.
src/err.ts(4,1): error TS2542: Index signature in type 'readonly string[]' only permits reading.
src/err.ts(5,6): error TS2339: Property 'sort' does not exist on type 'readonly string[]'.
The readonly modifier exists only in the type system. The compiled JavaScript is a normal array, and code that ignores the types can still change it. When runtime protection is needed, Object.freeze() blocks changes at runtime as well, and TypeScript types its result as a read-only array.
8. Tuples: Fixed-Length Arrays
A tuple is an array type with a fixed number of elements, where each position has its own type. The type [string, number] describes a pair such as a name and an age. Tuples are handy for small groups of values that belong together, such as the [key, value] pairs of a Map or the two values a function returns.
// 1. Fixed length, a type per position
const person: [string, number] = ["Lokesh", 37];
const age = person[1]; // age = 37, typed number
// 2. Labeled elements and an optional element
type Point = [x: number, y: number, z?: number];
const flat: Point = [1, 2];
const solid: Point = [1, 2, 3];
// 3. Destructuring
const [name, years] = person; // name = "Lokesh", years = 37
Reading a position that does not exist is a compile error, so person[2] fails with “Tuple type ‘[string, number]’ of length ‘2’ has no element at index ‘2’”. One gap remains, because a tuple is still an array at runtime and the compiler allows person.push(40) without an error. Writing the type as readonly [string, number] closes that gap.
Labels such as x and y change nothing at runtime; they only make the type and editor hints easier to read. Tuple types also support rest elements, such as [string, …number[]] for a name followed by any number of scores.
9. Running the Array Examples
Every snippet in this guide is part of a small Node.js project in the TypeScript-Examples repository. Each file in src holds one section, and src/index.ts runs them in order and prints the values from the comments. The project pins TypeScript 7.0.2 and requires Node.js 22 or newer.
The tsconfig.json sets lib to ES2024, which includes the ES2023 copying methods, and turns on strict mode.
{
"compilerOptions": {
"target": "es2024",
"lib": ["es2024"],
"module": "nodenext",
"strict": true,
"rootDir": ".",
"outDir": "dist",
"types": ["node"],
"skipLibCheck": true
},
"include": ["src"]
}
Inside the arrays folder, we install the compiler and run the project.
npm install
npm start
10. Conclusion
A TypeScript array is a JavaScript array with a checked element type, written as string[] or Array<string>, and it serves as the list type of the language. The at() method and the noUncheckedIndexedAccess option protect us from reading past the end. The ES2023 methods toSorted(), toReversed(), toSpliced() and with() change a copy instead of the original, and they pair well with readonly arrays, which block every changing method at compile time. Tuples cover the case of a short, fixed list with a type per position.
11. References
The MDN pages describe the runtime behavior of each method, and the TypeScript Handbook covers the array, read-only and tuple types.
- MDN: Array
- MDN: Array.prototype.toSorted()
- MDN: Array.prototype.with()
- MDN: structuredClone()
- TypeScript Handbook: Everyday Types (Arrays)
- TypeScript Handbook: Object Types (read-only arrays and tuples)
- TSConfig reference: noUncheckedIndexedAccess
Happy Learning !!
what about an array of type class?
I hate when they explain badly .. typescript sucks .. there is no information .. it makes no sense to explain array of one dimension .
how would I create a type for an array of vectors such as
var myArray = [ [1,2,3], [2,3,4], [3,4,5] ]
how to define the type of [ 1, ‘a’] ?
Arrays must be of similar data types. You cannot store integer and string inside single array.
Different data types can be stored provided the storing array is of type any, eg.
array1: number[] = [1,2,3];
arraystr: string[] = [“one”, “two”];
array3: any[] = […this.array1, …this.arraystr];