Back to List: ArrayList & LinkedList
Q6Question 6 of 7

What is the difference between Vector and ArrayList?

Key Concepts
  • Vector is the legacy (Java 1.0) synchronized version of ArrayList; every method is synchronized, making it thread-safe but slow
  • ArrayList (Java 1.2) is not synchronized and is significantly faster for single-threaded use because it avoids lock overhead
  • Vector grows by 2x (doubles) by default; ArrayList grows by 1.5x, wasting less memory
  • Vector has legacy methods like addElement(), elementAt(), elements() that return Enumeration instead of Iterator
  • Stack extends Vector, inheriting all its synchronization overhead; use ArrayDeque instead
  • For thread safety in modern Java, use Collections.synchronizedList() for simple cases or CopyOnWriteArrayList for read-heavy concurrent access
  • The only reason Vector and Stack exist in the JDK is backward compatibility with pre-1.2 code

Definition

Vector is a legacy synchronized ArrayList from Java 1.0 that locks every method and grows by 2x; use ArrayList for single-threaded code and ConcurrentHashMap patterns for multi-threaded code instead.
  • Vector (Java 1.0): every public method is synchronized, making it thread-safe but slow because even single-threaded code pays lock acquisition cost
  • ArrayList (Java 1.2): not synchronized, no lock overhead, preferred for all new code
  • Growth: Vector doubles capacity (2x); ArrayList grows by 1.5x, using memory more efficiently
  • Legacy API: Vector has addElement(), elementAt(), elements() (returns Enumeration); ArrayList uses standard add(), get(), iterator()
  • Stack: extends Vector; never use it in new code – use ArrayDeque instead

Vector is like a bank counter where every operation – even checking the time – requires locking the entire counter. Even when there is only one customer, the clerk locks the counter before each action and unlocks it after. ArrayList is a counter with no lock: the clerk serves instantly. When multiple customers arrive, you can add a lock selectively (synchronizedList) or use a modern multi-window counter (CopyOnWriteArrayList) where readers never wait.

Understanding the Concepts

What is the difference between Vector and ArrayList?

Vector and ArrayList are both backed by dynamic arrays, implement the List interface, and provide the same operations. The differences are in synchronization, growth strategy, and API age.

| Feature | Vector | ArrayList |
|---|---|---|
| Introduced | Java 1.0 | Java 1.2 |
| Synchronized | Yes (every method) | No |
| Growth | 2x (doubles) | 1.5x |
| Performance | Slow (lock overhead) | Fast |
| Null allowed | Yes | Yes |
| Legacy methods | addElement, elementAt, elements() | Standard Collection API |
| Thread safety | Built-in (coarse-grained) | Not thread-safe |
| Recommendation | Do not use | Use this |

JAVA
package com.bank.demo;

import java.util.*;

public class VectorVsArrayListDemo {
    public static void main(String[] args) {
        // Vector – synchronized, legacy
        Vector<String> vector = new Vector<>();
        vector.addElement("SAV2001"); // legacy method
        String first = vector.elementAt(0); // legacy method
        Enumeration<String> enumeration = vector.elements(); // legacy iterator

        // ArrayList – modern, faster
        List<String> arrayList = new ArrayList<>();
        arrayList.add("SAV2001");
        String firstAL = arrayList.get(0);
        Iterator<String> iterator = arrayList.iterator();

        System.out.println("Vector: " + vector);
        System.out.println("ArrayList: " + arrayList);
    }
}
TEXT
Vector: [SAV2001]
ArrayList: [SAV2001]

Why is Vector slow?

Every public method in Vector is declared synchronized. This means that before executing any operation – even size() or get() – the calling thread must acquire the object's intrinsic lock (monitor).

In single-threaded code, this lock acquisition and release is pure waste. It adds 15-30% overhead compared to ArrayList for the same operations.

In multi-threaded code, the synchronization is too coarse. All threads compete for a single lock on the entire Vector. While one thread is reading, no other thread can read or write. This creates a bottleneck that modern concurrent collections avoid with fine-grained locking.

JAVA
package com.bank.demo;

import java.util.*;

public class SynchronizationOverheadDemo {
    public static void main(String[] args) {
        int iterations = 10_000_000;

        // Vector – synchronized on every operation
        Vector<String> vector = new Vector<>();
        vector.add("SAV2001");
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            vector.get(0); // lock, get, unlock – every time
        }
        long vectorTime = System.nanoTime() - start;

        // ArrayList – no synchronization
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("SAV2001");
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            arrayList.get(0); // direct array access – no locking
        }
        long arrayListTime = System.nanoTime() - start;

        System.out.printf("Vector: %d ms%n", vectorTime / 1_000_000);
        System.out.printf("ArrayList: %d ms%n", arrayListTime / 1_000_000);
        System.out.printf("Vector is ~%.1fx slower%n", (double) vectorTime / arrayListTime);
    }
}
TEXT
Vector: 85 ms
ArrayList: 32 ms
Vector is ~2.7x slower

How is the growth strategy different?

Vector doubles its capacity by default (2x growth). You can also specify a custom increment: new Vector<>(10, 5) starts at capacity 10 and grows by 5 each time.

ArrayList grows by approximately 1.5x (the formula old + old >> 1).

The 2x growth wastes more memory. After reaching capacity 1024, Vector jumps to 2048 (1024 wasted slots). ArrayList would go to 1536 (512 wasted slots).

| Starting from 10 | Vector growth | ArrayList growth |
|---|---|---|
| Resize 1 | 10 to 20 | 10 to 15 |
| Resize 2 | 20 to 40 | 15 to 22 |
| Resize 3 | 40 to 80 | 22 to 33 |
| Resize 4 | 80 to 160 | 33 to 49 |
| Resize 5 | 160 to 320 | 49 to 73 |
| To reach 1M | ~17 resizes | ~40 resizes |
| Memory at 1M | Up to 2M capacity | Up to 1.5M capacity |

What is wrong with Stack?

Stack extends Vector, which means it inherits all of Vector's problems: synchronized every method, 2x growth, legacy API. Additionally, because Stack extends Vector, it exposes all of Vector's methods – including add(index, elem), remove(index), and get(index) – which violate the LIFO contract of a stack.

Use ArrayDeque as a stack instead. It is faster, uses less memory, and does not expose non-stack methods.

JAVA
package com.bank.demo;

import java.util.*;

public class StackVsArrayDequeDemo {
    public static void main(String[] args) {
        // BAD – Stack extends Vector (synchronized, legacy)
        Stack<String> legacyStack = new Stack<>();
        legacyStack.push("Step 1: Open account");
        legacyStack.push("Step 2: Deposit 50000");
        // Stack exposes Vector methods – breaks stack contract!
        legacyStack.add(0, "INSERTED AT BOTTOM"); // not stack behavior!

        // GOOD – ArrayDeque as stack
        Deque<String> modernStack = new ArrayDeque<>();
        modernStack.push("Step 1: Open account");
        modernStack.push("Step 2: Deposit 50000");
        System.out.println("Pop: " + modernStack.pop());
        System.out.println("Peek: " + modernStack.peek());
        // No add(index) method – clean stack API
    }
}
TEXT
Pop: Step 2: Deposit 50000
Peek: Step 1: Open account

What are the modern alternatives for thread safety?

| Need | Solution |
|---|---|
| Single-threaded | ArrayList (no synchronization) |
| Simple thread safety | Collections.synchronizedList(new ArrayList<>()) |
| Read-heavy concurrent | CopyOnWriteArrayList |
| Queue (producer-consumer) | ConcurrentLinkedQueue or LinkedBlockingQueue |
| Stack (concurrent) | ConcurrentLinkedDeque |

JAVA
package com.bank.service;

import java.util.*;
import java.util.concurrent.*;

public class ThreadSafeAlternativesDemo {
    public static void main(String[] args) {
        // Option 1: synchronizedList – simple but slow under contention
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        syncList.add("SAV2001");

        // Option 2: CopyOnWriteArrayList – ideal for read-heavy scenarios
        // Writes copy the entire array (expensive)
        // Reads are lock-free (fast)
        List<String> cowList = new CopyOnWriteArrayList<>();
        cowList.add("SAV2001");
        // 10,000 readers + 1 writer → perfect for event listener lists

        // Iteration is always safe (uses snapshot)
        for (String acc : cowList) {
            System.out.println("Safe iteration: " + acc);
        }
    }
}
TEXT
Safe iteration: SAV2001

What are common mistakes?

Mistake: Using Vector "because it is thread-safe"

JAVA
// WRONG – Vector's synchronization is too coarse and slow
List<String> accounts = new Vector<>();

// Also WRONG – even Vector is not safe for compound operations
Vector<String> v = new Vector<>();
if (!v.contains("SAV2001")) {
    v.add("SAV2001"); // RACE CONDITION! Another thread may add between check and add
}
// Vector synchronizes individual methods, not compound operations.
// You still need external synchronization for check-then-act patterns.

// CORRECT for compound operations
ConcurrentHashMap<String, Boolean> accounts = new ConcurrentHashMap<>();
accounts.putIfAbsent("SAV2001", Boolean.TRUE); // atomic check-and-add

What should a senior engineer know?

- Vector's compound action problem. Synchronizing individual methods does not make compound operations thread-safe. "Check if contains, then add" is still a race condition with Vector because the lock is released between contains() and add(). You need external synchronization or atomic operations.

- JIT optimization of locks. On HotSpot, the JIT compiler can sometimes eliminate locks on thread-local objects (lock elision) or replace contended locks with faster alternatives (lock coarsening). This reduces Vector's overhead in some cases, but it is not reliable enough to depend on.

- History. Vector was in Java from version 1.0 (1996). The Collections Framework was added in Java 1.2 (1998). Vector was retrofitted to implement List, which is why it has both legacy methods (elementAt) and modern methods (get). The same happened with Hashtable implementing Map.

- Stack is a design mistake. Stack should have been a interface or a composed class, not a subclass of Vector. By extending Vector, Stack inherits all Vector methods including random access – violating the LIFO abstraction. This is a textbook example of inheritance misuse.

What are common interview follow-up questions?

  • "Can you make ArrayList thread-safe?" – Yes, wrap with Collections.synchronizedList() or use CopyOnWriteArrayList.
  • "What is the growth factor of Vector?" – 2x by default, configurable via constructor.
  • "Why is Stack a bad design?" – It extends Vector, exposing non-stack operations and inheriting synchronization overhead.
  • "What is CopyOnWriteArrayList?" – A thread-safe list where writes copy the entire array, making reads lock-free.

Visual Overview

DIAGRAM
  Vector vs ArrayList timeline

  Java 1.0 (1996)     Java 1.2 (1998)     Modern Java
  ┌──────────┐        ┌──────────┐        ┌─────────────────────┐
  │ Vector   │        │ ArrayList│        │ CopyOnWriteArrayList│
  │ Stack    │        │ LinkedList│       │ ConcurrentHashMap   │
  │ Hashtable│        │ HashMap  │        │ ConcurrentLinkedQueue│
  └──────────┘        └──────────┘        └─────────────────────┘
  synchronized         not synchronized    fine-grained concurrency
  every method         (faster)            (fastest)

  Vector method overhead:
  ┌─────────────────────────────────┐
  │ Thread calls vector.get(0)      │
  │  1. Acquire monitor lock        │ ← overhead
  │  2. Read elementData[0]         │ ← actual work
  │  3. Release monitor lock        │ ← overhead
  └─────────────────────────────────┘

  ArrayList: just step 2.

Key Takeaway

Vector is a relic from Java 1.0 that synchronizes every method – add, get, remove, even size. This makes it thread-safe for individual operations but slow for everything else. Single-threaded code pays a 15-30% overhead for locks it does not need. Multi-threaded code hits a bottleneck because all threads compete for one lock.

ArrayList replaced Vector in Java 1.2 with the Collections Framework. It provides the same dynamic array behavior without synchronization. For thread safety, use Collections.synchronizedList() (simple but coarse-grained), CopyOnWriteArrayList (read-heavy, writes copy the array), or concurrent collections designed for specific access patterns.

Stack extends Vector, compounding the problem by exposing non-stack methods like add(index, elem) that violate the LIFO contract. Use ArrayDeque for both stack and queue behavior – it is faster, lighter, and has a cleaner API.

Remember one sentence: Vector is legacy, slow, and its synchronization does not even protect compound operations – use ArrayList plus modern concurrent collections instead.

---

Share
Use ← → arrow keys to navigate