DSA Tutorial
🔍

Array Time & Space Complexity

The Complete Array Operation Complexity Table

This is the definitive reference for every fundamental array operation. The reasons behind each complexity are explained in the sections below.

OperationTime ComplexitySpace ComplexityNotes
Access by indexO(1)O(1)Direct address computation
Update by indexO(1)O(1)Same as access
Search by value (unsorted)O(n)O(1)Must scan every element
Search by value (sorted)O(log n)O(1)Binary search
Insert at endO(1) amortizedO(1)Dynamic array may resize
Insert at index kO(n)O(1)Shifts n-k elements right
Insert at beginningO(n)O(1)Shifts all n elements right
Delete from endO(1)O(1)Decrement size only
Delete at index kO(n)O(1)Shifts n-k-1 elements left
Delete from beginningO(n)O(1)Shifts all n-1 elements left
Traverse all elementsO(n)O(1)Every element visited once
Reverse in-placeO(n)O(1)Two-pointer swap, n/2 swaps
Rotate by kO(n)O(1)Three-reversal technique
Copy entire arrayO(n)O(n)New allocation of size n
Fill entire arrayO(n)O(1)Writes to each of n positions

Why Access Is O(1)

Array elements are stored in a contiguous block of memory. The address of any element is computed directly:

address of arr[i] = base_address + (i × element_size)

This is one multiplication and one addition — a fixed number of steps regardless of array size or index position. Whether you access the first element or the millionth, exactly the same computation happens.

This is the only data structure where access by position is O(1). Linked lists are O(n) — you must follow pointers from the head. Trees are O(log n) — you must traverse from the root. Arrays are unique because memory is directly addressable by offset.

Why Insert and Delete Are O(n)

Because array elements must stay contiguous in memory, inserting or deleting anywhere except the end requires shifting elements to fill or create space.

Delete at index 2 in [A, B, C, D, E]:

Before:  [A, B, C, D, E]
                 ↑ delete here

Shift left from index 3 onward:
  arr[2] ← arr[3]  → [A, B, D, D, E]
  arr[3] ← arr[4]  → [A, B, D, E, E]

After:   [A, B, D, E]

Elements shifted: n - k - 1
When k = 0 (delete first): shift n-1 elements → O(n)
When k = n-1 (delete last): shift 0 elements → O(1)

The number of shifts is n - k - 1 for deletion and n - k for insertion. In the worst case (k = 0), all n elements shift. In the best case (k = n - 1), zero elements shift. Big-O measures the worst case — so insert and delete are O(n).

Amortized O(1) for Dynamic Array Append

Dynamic arrays (ArrayList, Python list, C++ vector, JavaScript array) appear to append in O(1), but occasionally trigger a resize — copying all elements to a larger block — which costs O(n).

How can the overall cost still be O(1)?

Python list resizes at: 0, 4, 8, 16, 32, 64, 128, ...  (roughly doubles)

Insertions 1-4:   no resize    → 4 cheap operations
Insertion  5:     resize (copy 4 elements) + insert
Insertions 6-8:   no resize    → 3 cheap operations
Insertion  9:     resize (copy 8 elements) + insert
Insertions 10-16: no resize    → 7 cheap operations
Insertion 17:     resize (copy 16 elements) + insert

Total work for n insertions:
  Insertions themselves:  n operations
  All resize copies:      1 + 2 + 4 + 8 + ... + n/2 ≈ n operations (geometric sum)

Total: 2n operations for n insertions
Amortized per insertion: 2n / n = 2 = O(1)

Each individual resize is O(n), but resizes happen exponentially less frequently as the array grows. Spread across all insertions, the cost per operation averages to O(1). This is amortized O(1) — the total work divided by the number of operations.

Java
1import java.util.ArrayList; 2import java.util.List; 3 4public class AmortizedAppend { 5 6 public static void main(String[] args) { 7 List<Integer> arr = new ArrayList<>(); 8 9 // Each append is O(1) amortized — occasional O(n) resize is absorbed 10 long start = System.nanoTime(); 11 12 for (int i = 0; i < 1_000_000; i++) { 13 arr.add(i); // O(1) amortized 14 } 15 16 long end = System.nanoTime(); 17 18 System.out.println("Appended 1,000,000 elements"); 19 System.out.println("Final size: " + arr.size()); 20 System.out.printf("Total time: %.2f ms%n", (end - start) / 1_000_000.0); 21 // Time grows linearly with n, not quadratically — O(n) total, O(1) per append 22 } 23}
Output (approximate — varies by machine):
Appended 1,000,000 elements
Final size: 1000000
Total time: ~15-50 ms

Algorithm Complexity on Arrays

Beyond the basic operations, every algorithm applied to an array has its own complexity. This is the reference for the most common algorithms.

AlgorithmTimeSpaceRequires
Linear SearchO(n)O(1)Nothing — works on any array
Binary SearchO(log n)O(1)Sorted array
Bubble SortO(n²)O(1)Nothing
Selection SortO(n²)O(1)Nothing
Insertion SortO(n²) avg, O(n) bestO(1)Best on nearly sorted
Merge SortO(n log n)O(n)Extra array for merge step
Quick SortO(n log n) avg, O(n²) worstO(log n) stackIn-place partition
Prefix Sum BuildO(n)O(n)New array of size n
Prefix Sum QueryO(1)O(1)Prefix array already built
Kadane's AlgorithmO(n)O(1)Maximum subarray sum
Two-Sum (Hash Map)O(n)O(n)Hash map of size n

Space Complexity of Common Array Patterns

Space complexity in array problems depends on whether you create auxiliary data structures.

O(1) Auxiliary Space — In-Place

Operations that modify the existing array without allocating new memory.

  • Reverse in-place: two pointer variables and one temp → O(1)
  • Rotate in-place: three pointer variables → O(1)
  • Swap two elements: one temp variable → O(1)
  • Two-pointer scan (palindrome, pair sum): two index variables → O(1)
  • Kadane's algorithm (maximum subarray): two running variables → O(1)
Java
1public class O1SpacePatterns { 2 3 // Maximum subarray sum — O(n) time, O(1) space (Kadane's algorithm) 4 public static int maxSubarraySum(int[] arr) { 5 int maxSum = arr[0]; 6 int currentSum = arr[0]; 7 8 for (int i = 1; i < arr.length; i++) { 9 // Either extend the existing subarray or start fresh at arr[i] 10 currentSum = Math.max(arr[i], currentSum + arr[i]); 11 maxSum = Math.max(maxSum, currentSum); 12 } 13 14 return maxSum; 15 } 16 17 public static void main(String[] args) { 18 int[] arr = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; 19 System.out.println("Array: " + java.util.Arrays.toString(arr)); 20 System.out.println("Max subarray sum: " + maxSubarraySum(arr)); 21 // Only 2 extra variables used — O(1) space regardless of n 22 } 23}
Output:
Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Max subarray sum: 6

O(n) Auxiliary Space — New Arrays or Hash Structures

Patterns that create new data structures proportional to input size.

  • Prefix sum array: new int array of size n → O(n)
  • Hash map for frequency or lookup: up to n entries → O(n)
  • Copying array: new allocation of size n → O(n)
  • Merge sort merge step: auxiliary array of size n → O(n)
Java
1import java.util.HashMap; 2import java.util.Map; 3 4public class OnSpacePatterns { 5 6 // Prefix sum — O(n) build space, O(1) per query after 7 public static int[] buildPrefixSum(int[] arr) { 8 int[] prefix = new int[arr.length]; // O(n) extra space 9 prefix[0] = arr[0]; 10 11 for (int i = 1; i < arr.length; i++) { 12 prefix[i] = prefix[i - 1] + arr[i]; 13 } 14 15 return prefix; 16 } 17 18 // Range sum query — O(1) after prefix sum is built 19 public static int rangeSum(int[] prefix, int left, int right) { 20 if (left == 0) return prefix[right]; 21 return prefix[right] - prefix[left - 1]; 22 } 23 24 // Count pairs with sum = target — O(n) time, O(n) space 25 public static int countPairs(int[] arr, int target) { 26 Map<Integer, Integer> freq = new HashMap<>(); // O(n) space 27 int count = 0; 28 29 for (int num : arr) { 30 int complement = target - num; 31 count += freq.getOrDefault(complement, 0); 32 freq.put(num, freq.getOrDefault(num, 0) + 1); 33 } 34 35 return count; 36 } 37 38 public static void main(String[] args) { 39 int[] arr = {3, 1, 4, 1, 5, 9, 2, 6}; 40 41 int[] prefix = buildPrefixSum(arr); 42 System.out.println("Sum [2..5]: " + rangeSum(prefix, 2, 5)); 43 System.out.println("Sum [0..7]: " + rangeSum(prefix, 0, 7)); 44 45 int[] arr2 = {1, 3, 2, 4, 5}; 46 System.out.println("Pairs summing to 5: " + countPairs(arr2, 5)); 47 } 48}
Output:
Sum [2..5]: 15
Sum [0..7]: 31
Pairs summing to 5: 2

Brute Force vs Optimized: Complexity Comparison

The difference between acceptable and unacceptable solutions is almost always the complexity of the inner operation. This table shows the most common array problems with their brute force and optimized complexities.

ProblemBrute ForceOptimizedTechnique
Find two numbers summing to targetO(n²) time, O(1) spaceO(n) time, O(n) spaceHash Map
Maximum sum subarrayO(n²) time, O(1) spaceO(n) time, O(1) spaceKadane's Algorithm
Count subarrays with sum kO(n²) time, O(1) spaceO(n) time, O(n) spacePrefix Sum + Hash Map
Find all duplicatesO(n²) time, O(1) spaceO(n) time, O(n) spaceHash Set
Longest consecutive sequenceO(n²) time, O(1) spaceO(n) time, O(n) spaceHash Set
Maximum in sliding window (size k)O(nk) time, O(1) spaceO(n) time, O(k) spaceMonotonic Deque
Sort an arrayO(n²) time, O(1) spaceO(n log n) time, O(log n) spaceQuick Sort / Merge Sort
Binary search on sorted arrayO(n) time, O(1) spaceO(log n) time, O(1) spaceBinary Search

The pattern is consistent: a brute force inner loop searching or comparing across the array is O(n) per outer iteration = O(n²) total. The optimization is either precomputing a lookup structure (hash map → O(1) lookup) or maintaining a running invariant (sliding window, Kadane's → O(1) per step).

Reading Constraints to Predict Required Complexity

In interview problems, the constraint on n is not decoration — it is a hint about what complexity is expected. This is how experienced engineers read constraints.

n ≤ 10:
  Any complexity works including O(n!)
  Brute force backtracking is fine.

n ≤ 100:
  O(n²) or O(n³) is acceptable.
  Nested loops fine.

n ≤ 1,000:
  O(n²) still fine (~1 million operations).
  Bubble sort, selection sort acceptable.

n ≤ 100,000 (10^5):
  O(n²) is too slow (~10 billion operations).
  Need O(n log n) or O(n).
  Sorting-based or hash-based solutions required.

n ≤ 1,000,000 (10^6):
  O(n log n) is borderline (~20 million operations).
  O(n) strongly preferred.
  Hash map, prefix sum, sliding window, Kadane's.

n ≤ 10^9:
  O(n) is too slow.
  Need O(log n) or O(1).
  Binary search on sorted/answer space.

Applying This in Practice

When you read a constraint, immediately ask: given this n, what is the maximum complexity my solution can have?

Problem: "Given n integers (n ≤ 10^5), find all pairs with sum = target."

Constraint: n = 100,000
Max acceptable complexity: O(n log n) or O(n)

Brute force: O(n²) → 10^10 operations → too slow
Sorting + two pointers: O(n log n) → acceptable
Hash map: O(n) → optimal

The Space-Time Tradeoff in Arrays

Every array optimization involves trading memory for speed. The three most common tradeoffs:

Tradeoff 1 — Hash Map for Lookup

Store elements as you scan to avoid re-scanning:

Brute force: scan array for each element  → O(n²) time, O(1) space
Optimized:   hash map for O(1) lookup     → O(n)  time, O(n) space

Cost: O(n) extra space
Gain: O(n²) → O(n) time

Tradeoff 2 — Prefix Array for Range Queries

Precompute cumulative sums to answer range queries instantly:

Without prefix: recompute range sum each time → O(n) per query
With prefix:    build once, query in O(1)      → O(1) per query after O(n) build

Cost: O(n) extra space for prefix array
Gain: O(n) → O(1) per range query

Tradeoff 3 — Copy for Safety

Copy an array before modifying when the original must be preserved:

In-place modification: O(1) extra space, original is lost
Copy then modify:      O(n) extra space, original preserved

Cost: O(n) space
Gain: original data preserved for further use

Understanding these tradeoffs lets you state them explicitly in interviews — a sign that your reasoning is engineering-grade, not just academic.

Worst Case, Best Case, and Average Case for Array Algorithms

Not all complexities are equal across all inputs. For array algorithms specifically:

AlgorithmBest CaseAverage CaseWorst CaseWhen best case triggers
Linear SearchO(1)O(n)O(n)Target is first element
Binary SearchO(1)O(log n)O(log n)Target is the midpoint on first check
Bubble SortO(n)O(n²)O(n²)Array already sorted (with early exit)
Quick SortO(n log n)O(n log n)O(n²)Pivot always chosen at median
Kadane's AlgorithmO(n)O(n)O(n)Always linear — no variation
Hash Map LookupO(1)O(1)O(n)No hash collisions (n collisions = O(n))

Quick Sort's worst case (O(n²)) occurs when the pivot is always the smallest or largest element — typically on already-sorted input. This is why production implementations use randomized pivots or the median-of-three rule.

Hash map lookup is O(n) in the absolute worst case (all keys hash to the same bucket), but this is essentially impossible with a good hash function on random data.

Practical Complexity Checklist for Array Problems

Before finalizing any array solution, run through this checklist:

Step 1: What is n?
  → Read the constraint. Determine max acceptable complexity.

Step 2: How many loops touch the array?
  One loop:          O(n)    time
  Two sequential:    O(n)    time  (add, don't multiply)
  Two nested:        O(n²)   time  (multiply)
  Loop + binary:     O(n log n) time

Step 3: What auxiliary structures are created?
  No new arrays/maps:         O(1) space
  One array of size n:        O(n) space
  One array of size k < n:    O(k) space
  Two arrays of size n:       O(n) space (constants drop)
  2D array of size n×n:       O(n²) space

Step 4: Is the space tradeoff justified?
  O(n) space for O(n²) → O(n) time: almost always yes
  O(n) space for O(n) → O(1) time: depends on query count

Step 5: Are there hidden O(n) operations inside loops?
  Common traps:
  - list.contains(x) inside a loop → O(n²) total → use a set
  - arr.indexOf(x) inside a loop   → O(n²) total → use a map
  - arr.remove(x) inside a loop    → O(n²) total → mark and sweep

Interview Questions

Q: What is amortized O(1) and how does it apply to dynamic arrays?

Amortized O(1) means the average cost per operation over a sequence of operations is O(1), even if individual operations are occasionally expensive. Dynamic arrays double their capacity when full, copying all elements — an O(n) operation. But because doubling happens exponentially less often as the array grows, the total work across n appends is O(n), giving O(1) per append on average. Single worst-case appends are O(n), but you cannot trigger them repeatedly.

Q: Why is binary search O(log n) while linear search is O(n)?

Binary search eliminates half the remaining elements at each step. Starting with n elements, after one step you have n/2, then n/4, then n/8. The number of steps to reach 1 element is log₂(n). Linear search eliminates only one element per step — taking up to n steps. Binary search requires a sorted array because the comparison at each midpoint must meaningfully tell you which half contains the target.

Q: Why does merge sort use O(n) space while quick sort uses O(log n)?

Merge sort needs an auxiliary array of size n to hold merged results during the merge step. It cannot merge two subarrays in-place efficiently. Quick sort partitions in-place — no auxiliary array — but uses O(log n) space for the recursion stack (the depth of recursive calls on average, since each call halves the problem). In the worst case quick sort's stack is O(n).

Q: If two sequential loops both touch all n elements, is the time complexity O(n) or O(2n)?

O(n). Sequential loops add their complexities: O(n) + O(n) = O(2n). Constants are dropped in Big-O analysis, giving O(n). Two sequential O(n) loops are still O(n) — not O(n²). Only nested loops multiply.

FAQs

Does the complexity of accessing an element change if the array is sorted?

No. Sorted or not, arr[i] is O(1) because index access uses the address formula directly. Sorting affects search complexity (enabling binary search at O(log n) instead of O(n)) but never affects access-by-index complexity, which is purely a memory address computation.

Is creating a copy of an array O(n) time or O(1)?

O(n) time and O(n) space. Every element must be read from the original and written to the new allocation — n reads and n writes. Arrays.copyOf in Java, slicing in Python (arr[:]), and spread in JavaScript ([...arr]) are all O(n). There is no shortcut — you cannot copy n values without touching each one.

Why is the worst-case complexity of hash map lookup O(n) if hash maps are supposedly fast?

Hash maps use a hash function to map keys to buckets. If many keys hash to the same bucket (a collision), they form a linked list or tree in that bucket. In the absolute worst case, all n keys go into the same bucket, making every lookup O(n). In practice with a good hash function this is astronomically unlikely, which is why the average case is O(1). Production hash maps use techniques like load factors, rehashing, and collision-resistant hash functions to ensure practical O(1) performance.

Does sorting an array before solving a problem always improve complexity?

No — sorting costs O(n log n), which is worse than O(n). If the problem can be solved in O(n) without sorting (using a hash map, prefix sum, or single-pass scan), sorting is counterproductive. Sorting helps when it enables O(log n) binary search or O(n) two-pointer techniques that would otherwise require O(n²) brute force — in those cases the O(n log n) sort cost is justified.

Quick Quiz

Question 1: An algorithm has one loop over n elements, and inside the loop it calls list.contains(x) which is O(n). What is the overall time complexity?

  • A) O(n)
  • B) O(n log n)
  • C) O(n²)
  • D) O(2n)

Answer: C) O(n²). The outer loop runs n times. Each iteration calls contains() which is O(n). Nested operations multiply: O(n) × O(n) = O(n²). This is the classic hidden-loop trap — replacing contains() with a set lookup (O(1)) reduces the total to O(n).

Question 2: A problem has n ≤ 10^6. Your solution uses two sequential loops, each O(n), and a hash map of size n. What is the time and space complexity?

  • A) O(n²) time, O(n) space
  • B) O(n) time, O(n) space
  • C) O(2n) time, O(n) space — different from O(n)
  • D) O(n log n) time, O(n) space

Answer: B) O(n) time, O(n) space. Two sequential loops add: O(n) + O(n) = O(2n) = O(n). The hash map is O(n) space. Both are acceptable for n ≤ 10^6.

Question 3: You append elements one at a time to a dynamic array using a doubling strategy. After n appends, what is the total time spent on resize operations?

  • A) O(n²) — each resize copies more elements
  • B) O(n) — geometric series sums to 2n
  • C) O(n log n) — log n resizes of increasing size
  • D) O(1) — resizes are negligible

Answer: B) O(n) — geometric series sums to 2n. Resize sizes are 1, 2, 4, 8, ..., n/2. Their sum = 1 + 2 + 4 + ... + n/2 = n - 1 ≈ O(n). Total work for n appends including all resizes is O(n), giving O(1) amortized per append.

Question 4: Which sorting algorithm has O(1) auxiliary space and O(n log n) average time?

  • A) Merge Sort — O(n log n) time, O(n) space
  • B) Bubble Sort — O(n²) time, O(1) space
  • C) Quick Sort — O(n log n) average time, O(log n) stack space
  • D) Insertion Sort — O(n²) average time, O(1) space

Answer: C) Quick Sort. Quick Sort partitions in-place using O(1) auxiliary array space. Its recursion stack uses O(log n) space on average (O(n) worst case). Average time is O(n log n). Merge Sort's merge step requires O(n) extra array space. Bubble and Insertion Sort are O(n²).

Summary

Array complexity is determined by two physical constraints: contiguous memory enables O(1) index access, and maintaining contiguity forces O(n) shifting for mid-array insert and delete.

The key complexities to carry as permanent intuition:

  • Access and update by index → always O(1), no exceptions
  • Insert and delete at end → O(1) amortized for dynamic arrays
  • Insert and delete at middle or beginning → O(n) because of shifting
  • Traversal of n elements → O(n) minimum — cannot skip elements
  • Binary search → O(log n) on sorted arrays
  • Sorting → O(n log n) optimal for comparison-based sorts
  • Hash map for lookup → O(n) space buys O(1) per lookup
  • Prefix array → O(n) space buys O(1) per range query

Read constraints before choosing an algorithm. n ≤ 10³ tolerates O(n²). n ≤ 10⁵ requires O(n log n) or better. n ≤ 10⁶ requires O(n). n ≤ 10⁹ requires O(log n) or O(1). The constraint tells you what complexity is acceptable — your job is to find a solution that fits within it.

In the next topic, you will explore 2D Arrays — extending these concepts to two-dimensional grids, matrix traversal, and the complexity of row-major vs column-major access patterns.

Array Time & Space Complexity