TypeScript Add Element to Array: push, unshift, splice

Add items to the end, start or any index of a TypeScript array, or to a new array with spread, concat() and toSpliced(), with the compiler errors explained.

angular-typescript

To add an element to a TypeScript array, we call push() to append it at the end or unshift() to put it at the beginning, and splice() inserts it at a given index. These methods change the array in place. When the original must stay as it is, the spread operator and concat() return a new array that contains the added items, and so does the ES2023 method toSpliced().

We add array items whenever new data arrives in our app, such as a product that a user puts in a shopping cart or a new message in a chat window.

The following example adds items in each of these ways, and the comments show the result of each call.

const fruits: string[] = ["apple", "banana"];

// 1. Add to the end
const length = fruits.push("cherry");         // length = 3

// 2. Add to the beginning
fruits.unshift("mango");                      // ["mango", "apple", "banana", "cherry"]

// 3. Insert at index 2
fruits.splice(2, 0, "kiwi");                  // ["mango", "apple", "kiwi", "banana", "cherry"]

// 4. Append all items of another array
fruits.push(...["grape", "lime"]);            // grape and lime at the end

// 5. New array, original unchanged
const nums = [1, 2, 3];
const atEnd = [...nums, 4];                   // atEnd = [1, 2, 3, 4]
const atStart = [0, ...nums];                 // atStart = [0, 1, 2, 3]
const merged = nums.concat([4, 5]);           // merged = [1, 2, 3, 4, 5]
const inserted = nums.toSpliced(1, 0, 9);     // inserted = [1, 9, 2, 3]

Notice that push() returns the new length, not the array, and that the last four lines leave nums as it was.

TypeScript arrays have no fixed size, so adding never requires creating a larger array first, as it does with Java arrays. What TypeScript adds on top of JavaScript is the type check, so every added value must match the element type, and pushing a string into a number[] fails at compile time.

Next, we look at each method with its return value, inserting at an index, appending a whole array, adding to a copy, and the compiler errors that appear when the array type is wrong, including the never[] error on empty arrays.

1. push(): Append to the End

The method push() adds items at the end of an array. It accepts one or more values, adds them after the last element in the order given, and returns the new length of the array, not the array itself. Because it only writes at the end, the existing elements do not move, which makes push() fast even for long arrays.

const nums: number[] = [1, 2, 3];

// 1. One item; push() returns the new length
const newLength = nums.push(4);               // newLength = 4, nums = [1, 2, 3, 4]

// 2. Several items in one call
nums.push(5, 6);                              // nums = [1, 2, 3, 4, 5, 6]

// 3. All items of another array
const more = [7, 8];
nums.push(...more);                           // nums = [1, 2, 3, 4, 5, 6, 7, 8]

The spread in step 3 matters, because nums.push(more) without the three dots tries to add the array [7, 8] as a single element. In plain JavaScript, nums.push(more) creates a nested array without any error, whereas in TypeScript it is a compile error because number[] is not a number.

A common mistake is to write const result = nums.push(4) and expect result to be the array, but it is a number. To get a new array with the extra element, we use the spread syntax from section 4.

1.1. Appending a Very Large Array

The spread inside push(…more) passes every element as a separate function argument. JavaScript engines limit the number of arguments a call can take, so a very large source array throws a RangeError. In our Node.js 22 test, 120,000 elements worked and 150,000 failed; the exact limit depends on the engine and its stack size.

const big = Array.from({ length: 150_000 }, (_, i) => i);
const target: number[] = [];

const count = target.push(...big);            // RangeError: Maximum call stack size exceeded
for (const n of big) target.push(n);          // works for any size

When the size of the source array is not under our control, for example rows from a file or an API, a loop with push() or a concat() call is the safe choice.

2. unshift(): Add to the Beginning

The method unshift() is the counterpart of push() for the front of the array. It inserts the given values at index 0, moves every existing element one or more places to the right, and returns the new length.

const names = ["Raj", "John"];

const size = names.unshift("Lokesh");         // size = 3, names = ["Lokesh", "Raj", "John"]
names.unshift("Amit", "Brian");               // names = ["Amit", "Brian", "Lokesh", "Raj", "John"]

When we pass several values in one call, they keep their order, so “Amit” comes before “Brian”. Calling unshift() once per value gives the reverse order, because each call puts its value in front of the previous one.

For example, a notification panel that shows the newest item first can call unshift() for each new notification, which is fine for a few dozen items. Moving every element has a cost, though. For an array of n elements, each unshift() call does work proportional to n, so calling it inside a loop over a large array gets slow. In that case, we push() the items and call reverse() once at the end, or build a new array with the spread syntax.

3. splice(): Insert at an Index

The method splice() can remove and insert elements at any position in one call. To insert without removing, we pass 0 as the second argument. For example, a playlist app that queues a song right after the one that is playing calls splice(current + 1, 0, song). The signature is splice(start, deleteCount, …items).

  • The start argument is the index where the change begins. A negative value counts from the end.
  • The deleteCount argument is how many existing elements to remove from start. We pass 0 to only insert.
  • The items arguments are the values to insert at start, in order.
const nums = [1, 2, 5];

// 1. Insert at index 2, remove nothing
const removed = nums.splice(2, 0, 3, 4);      // removed = [], nums = [1, 2, 3, 4, 5]

// 2. Negative index counts from the end
nums.splice(-1, 0, 9);                        // nums = [1, 2, 3, 4, 9, 5]

// 3. Index past the end appends
nums.splice(100, 0, 6);                       // nums = [1, 2, 3, 4, 9, 5, 6]

The method splice() returns an array of the removed elements, which is empty when deleteCount is 0. An index past the end does not throw an error and does not create empty slots; the items are added at the end. The same method also removes items from an array when deleteCount is greater than 0.

4. Adding to a New Array Instead

The methods push() and unshift() change the array that other parts of the program may also hold, and so does splice(). Changing an array in place causes problems in these cases.

  • The array is a function argument, so the caller sees the change.
  • The array is React or Redux state, where a changed array with the same reference does not trigger a re-render.
  • The array is typed as readonly.

In all of these cases, we build a new array that contains the old items plus the new ones.

const nums = [1, 2, 3];

// 1. Spread: any position
const atEnd = [...nums, 4];                   // atEnd = [1, 2, 3, 4]
const atStart = [0, ...nums];                 // atStart = [0, 1, 2, 3]
const both = [...nums, ...[4, 5]];            // both = [1, 2, 3, 4, 5]

// 2. concat(): values and arrays
const joined = nums.concat(4, [5, 6]);        // joined = [1, 2, 3, 4, 5, 6]

// 3. toSpliced(): insert at an index (ES2023)
const inserted = nums.toSpliced(1, 0, 9);     // inserted = [1, 9, 2, 3]

// nums is unchanged: [1, 2, 3]

The options differ in what they accept.

  • The spread operator is the most flexible option because the new items can go at any position, including between two arrays.
  • The method concat() accepts single values and arrays in the same call and opens each array argument by one level.
  • The method toSpliced() takes the same arguments as splice() but returns a new array; it needs lib ES2023 or later in tsconfig.json and Node.js 20 or later.

A readonly array has no push() method at all, so a new array is the only way to add to it.

const days: readonly string[] = ["Mon", "Tue"];

const week = [...days, "Wed"];                // week = ["Mon", "Tue", "Wed"]

Copying has a price. Each spread or concat() call copies all existing elements, so writing list = […list, item] inside a loop copies the whole array on every pass. For 10,000 items, that is about 50 million element copies instead of 10,000 pushes. Inside a loop, we push() into a local array and hand out the finished array afterwards.

5. Typing Rules When Adding Items

The compiler checks every value we add against the element type of the array. Most of the time that catches real mistakes, such as a string read from a form being pushed into a number array. A few errors confuse people, though, and the most confusing one involves an empty array inside an object literal.

const nums: number[] = [1, 2];
nums.push("3");
nums.push([3, 4]);

const cart = { items: [] };
cart.items.push("apple");

interface Person { name: string; age: number; }
const people: Person[] = [];
people.push({ name: "Lokesh", agee: 37 });

const days: readonly string[] = ["Mon", "Tue"];
days.push("Wed");

TypeScript 7.0 reports one error for each push() call.

src/err.ts(2,11): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
src/err.ts(3,11): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
src/err.ts(6,17): error TS2345: Argument of type '"apple"' is not assignable to parameter of type 'never'.
src/err.ts(10,31): error TS2561: Object literal may only specify known properties, but 'agee' does not exist in type 'Person'. Did you mean to write 'age'?
src/err.ts(13,6): error TS2339: Property 'push' does not exist on type 'readonly string[]'.

The never error on line 6 comes from type inference. An empty [] inside an object literal has no elements to infer from, so in strict mode TypeScript gives the property the type never[], which is an array that can hold nothing. The fix is to declare the type of the property, or of the whole object. For arrays of objects, we use an interface, and for arrays that hold more than one kind of value, we use a union element type.

// 1. Empty array in an object literal: give the property a type
const cart: { items: string[] } = { items: [] };
cart.items.push("apple");                     // items = ["apple"]

// 2. Array of objects: every pushed object is checked
interface Person { name: string; age: number; }
const people: Person[] = [];
people.push({ name: "Lokesh", age: 37 });     // people.length = 1

// 3. Union element type
const values: (string | number)[] = [];
values.push("apple", 5);                      // values = ["apple", 5]

The agee typo in the failing snippet shows why typing an array of objects is worth it. The compiler compares each pushed object literal with the Person interface and rejects unknown properties, so the misspelled field never reaches the data. The same check applies to every TypeScript array of objects, whether we add items with push() or with the spread syntax.

6. Adding an Item Only If It Is Missing

Arrays accept duplicates, but a tag picker, for example, must not add the same tag twice when the user clicks it again. When a value should appear only once, we check with includes() before adding it. For many values, a Set is the better structure, because it ignores duplicates by design and its has() check does not scan the whole collection.

const fruits = ["apple", "banana"];

// 1. Check first with includes()
if (!fruits.includes("apple")) {
  fruits.push("apple");                       // skipped, already there
}

// 2. Set for many unique values
const unique = new Set(fruits);
unique.add("cherry");
unique.add("apple");                          // ignored
const list = [...unique];                     // list = ["apple", "banana", "cherry"]

The method includes() compares with the === rule (plus NaN equal to NaN), so it works for strings and numbers but not for objects with the same fields. For objects, we check with some() and a condition on a key field, for example people.some((p) => p.name === “Raj”).

7. Which Method to Use

The choice depends on two questions, namely where the new items go and whether other code holds a reference to the original array.

GoalChanges the original arrayReturns a new array
Add at the endpush(item)[…arr, item] or concat(item)
Add at the beginningunshift(item)[item, …arr]
Insert at index isplice(i, 0, item)toSpliced(i, 0, item)
Append another arraypush(…other) (small arrays)[…arr, …other] or concat(other)
Return valuepush(), unshift(): new length; splice(): removed itemsThe new array

For local arrays that we build and own, the methods in the first column are faster and clearer. For state, parameters and readonly arrays, the second column avoids side effects. ES2023 also added other copying array methods, such as toSorted() and with().

8. Running the Add and Append Examples

Each snippet is part of the runnable project in the typescript-array-add-append-items folder on GitHub. Each section has its own file under src, and src/index.ts calls them one after another, printing the arrays after each step. The project uses TypeScript 7.0.2 and Node.js 22 or newer, with lib set to ES2024 so that toSpliced() compiles.

npm install
npm start

9. Conclusion

We add to the end of a TypeScript array with push() and to the start with unshift(), whereas splice(index, 0, …items) inserts at any index. These methods change the array and return either a number or the removed items, not the array. When the original must stay unchanged, we build a new array with the spread syntax or concat(), or insert into a copy with toSpliced(). The compiler checks every added value, and the one error that needs a deliberate fix is never[] on an untyped empty array inside an object.

10. References

Each MDN page lists the exact parameters and return values, and the TypeScript Handbook explains how array element types are inferred.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.