Arrays Performance in TypeScript
Arrays in TypeScript are dynamic arrays that can grow and shrink. Understanding their performance characteristics helps you write efficient code.
Why this matters
Arrays are the most common data structure, but their performance varies dramatically depending on which operation you’re performing. Using the wrong operation can turn a fast algorithm into a slow one.
Core concepts
- Arrays are stored contiguously in memory - this enables O(1) index access
- Dynamic arrays resize - when full, they allocate a larger chunk and copy elements
- Operations at the end are fast - O(1) amortized
- Operations at the beginning are slow - O(n) because elements must shift
- Searching requires iteration - O(n) worst case
Big O Complexity
Access Operations
Access by index: O(1) Arrays provide direct access to any element by its index. The computer knows exactly where the element is in memory.
const arr = [10, 20, 30, 40, 50];
const value = arr[2]; // O(1) - instant accessSearch by value: O(n) To find an element by value, you must check each element until you find it (worst case).
function findIndex(arr: number[], target: number): number {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i; // O(n) - may need to check all elements
}
return -1;
}Insert Operations
Insert at end (push): O(1) amortized Adding to the end is usually O(1), but occasionally O(n) when the array needs to resize. On average, it’s O(1).
const arr = [1, 2, 3];
arr.push(4); // O(1) amortized - usually fast, occasionally needs resizeInsert at beginning (unshift): O(n) Adding to the beginning requires shifting all existing elements one position to the right.
const arr = [2, 3, 4];
arr.unshift(1); // O(n) - must shift all 3 elements
// Result: [1, 2, 3, 4]Insert at specific index (splice): O(n) Inserting at any position requires shifting elements. The closer to the beginning, the more elements need to shift.
const arr = [1, 2, 4, 5];
arr.splice(2, 0, 3); // O(n) - inserts 3 at index 2, shifts elements after
// Result: [1, 2, 3, 4, 5]Delete Operations
Delete at end (pop): O(1) Removing from the end is fast - no shifting needed.
const arr = [1, 2, 3, 4];
arr.pop(); // O(1) - removes last element, no shifting
// Result: [1, 2, 3]Delete at beginning (shift): O(n) Removing from the beginning requires shifting all remaining elements one position to the left.
const arr = [1, 2, 3, 4];
arr.shift(); // O(n) - must shift all 3 remaining elements
// Result: [2, 3, 4]Delete at specific index (splice): O(n) Deleting at any position requires shifting elements. The closer to the beginning, the more elements need to shift.
const arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // O(n) - removes element at index 2, shifts remaining
// Result: [1, 2, 4, 5]Other Operations
Slice: O(n) Creates a new array containing a portion of the original. Must copy all elements in the slice.
const arr = [1, 2, 3, 4, 5];
const slice = arr.slice(1, 4); // O(n) - creates new array [2, 3, 4]Concat: O(n + m) Creates a new array by combining two arrays. Must copy all elements from both.
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = arr1.concat(arr2); // O(n + m) - copies all elementsIncludes/IndexOf: O(n) Must check each element until found or end of array.
const arr = [1, 2, 3, 4, 5];
arr.includes(3); // O(n) - checks elements until found
arr.indexOf(3); // O(n) - checks elements until foundWhen arrays are fast
✅ Accessing elements by index - O(1) direct access
const users = ['Alice', 'Bob', 'Charlie'];
const firstUser = users[0]; // O(1) - instant✅ Adding/removing at the end - O(1) amortized
const stack: number[] = [];
stack.push(1); // O(1) - fast
stack.push(2); // O(1) - fast
stack.pop(); // O(1) - fast✅ Iterating through elements - O(n) but efficient
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
console.log(num); // O(n) - one pass through
}When arrays are slow
❌ Searching for a value - O(n) worst case
// Slow: O(n) search
function hasValue(arr: number[], target: number): boolean {
return arr.includes(target); // O(n) - checks every element
}
// Better: Use Set or Map for O(1) lookup
const numSet = new Set([1, 2, 3, 4, 5]);
numSet.has(3); // O(1) - instant lookup❌ Inserting/deleting at the beginning - O(n) due to shifting
// Slow: O(n) per operation
const queue: number[] = [];
queue.unshift(1); // O(n) - shifts all elements
queue.unshift(2); // O(n) - shifts all elements again
queue.shift(); // O(n) - shifts all elements
// Better: Use a proper Queue data structure or reverse array❌ Frequent insertions/deletions in the middle - O(n) per operation
// Slow: O(n) per insertion
const arr = [1, 2, 4, 5];
arr.splice(2, 0, 3); // O(n) - shifts elements
// Consider: Linked list if frequent middle insertions neededCommon pitfalls
- Using includes() in a loop: Creates O(n²) complexity
// ❌ Bad: O(n²)
function removeDuplicates(arr: number[]): number[] {
const result: number[] = [];
for (const num of arr) {
if (!result.includes(num)) { // O(n) inside O(n) loop
result.push(num);
}
}
return result;
}
// ✅ Good: O(n)
function removeDuplicates(arr: number[]): number[] {
const seen = new Set<number>();
const result: number[] = [];
for (const num of arr) {
if (!seen.has(num)) { // O(1) lookup
seen.add(num);
result.push(num);
}
}
return result;
}- Using shift()/unshift() for queue operations: O(n) per operation
// ❌ Bad: O(n) per operation
const queue: number[] = [];
queue.unshift(1); // O(n)
queue.unshift(2); // O(n)
queue.shift(); // O(n)
// ✅ Good: Use array as stack (push/pop) or implement proper queue
const queue: number[] = [];
queue.push(1); // O(1)
queue.push(2); // O(1)
queue.shift(); // Still O(n), but less common operation- Creating new arrays unnecessarily: O(n) space and time
// ❌ Bad: Creates new array every iteration
function doubleValues(arr: number[]): number[] {
let result: number[] = [];
for (const num of arr) {
result = result.concat([num * 2]); // O(n) per concat
}
return result;
}
// ✅ Good: Pre-allocate or use map
function doubleValues(arr: number[]): number[] {
return arr.map(num => num * 2); // O(n) single pass
}When to use arrays
- ✅ You need ordered elements
- ✅ You access elements by index frequently
- ✅ You add/remove primarily at the end (stack operations)
- ✅ You iterate through all elements
- ✅ You need a simple, built-in data structure
When NOT to use arrays
- ❌ You need fast value lookups → Use
SetorMap - ❌ You frequently insert/delete at the beginning → Use
QueueorDeque - ❌ You need fast insertions in the middle → Consider
Linked List - ❌ You’re checking membership frequently → Use
Set