What is Vector and how does it differ from ArrayList
While Vector and ArrayList share a near-identical internal data structure (both encapsulate an Object[] buffer), Vector is thread-safe by default, synchronizing nearly all of it...
🟢 Junior Level
Vector is a legacy dynamic array class introduced in Java 1.0, prior to the standardization of the Java Collections Framework (JCF) in Java 1.2. In Java 1.2, Vector was retrofitted to implement the java.util.List interface.
While Vector and ArrayList share a near-identical internal data structure (both encapsulate an Object[] buffer), Vector is thread-safe by default, synchronizing nearly all of its public methods with the synchronized keyword.
Core Comparison Matrix
| Architectural Feature | Vector (Legacy JDK 1.0) |
ArrayList (Modern JDK 1.2+) |
|---|---|---|
| Thread Synchronization | Synchronized (methods acquire this monitor) |
Unsynchronized (Lock-free single-thread) |
| Execution Performance | Slow (unnecessary monitor acquisition overhead) | Blazing Fast (zero locking overhead) |
| Default Resizing Factor | Doubles capacity ($2.0\times$) | Scales by $1.5\times$ (old + (old >> 1)) |
| Configurable Growth Step | Supports explicit capacityIncrement |
Fixed $1.5\times$ factor |
| Traversal Mechanics | Supports legacy Enumeration and modern Iterator |
Supports Iterator and Spliterator |
| Current Best Practice | Obsolete / Deprecated in spirit | Standard production default |
// ❌ OBSOLETE ANTI-PATTERN:
List<String> vector = new Vector<>();
// ✅ MODERN PRODUCTION STANDARD (Single-threaded):
List<String> list = new ArrayList<>();
// ✅ MODERN PRODUCTION CONCURRENT ALTERNATIVES:
List<String> cowList = new CopyOnWriteArrayList<>();
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
🟡 Middle Level
1. The Fallacy of Built-In Thread-Safety: Check-Then-Act Race Conditions
A pervasive misconception is that Vector makes multi-threaded business logic automatically thread-safe.
Synchronizing individual methods guarantees only that isolated invocations (a single get() or a single add()) execute atomically. It provides zero protection for compound operations (Check-Then-Act sequences):
// ❌ RACE CONDITION: Vector does NOT prevent this failure!
if (!vector.isEmpty()) {
// Thread B can intervene here and execute vector.clear() or vector.remove(0)!
String item = vector.remove(0); // 💥 Throws ArrayIndexOutOfBoundsException!
}
// ❌ RACE CONDITION on duplicate verification:
if (!vector.contains(target)) {
// Thread B can insert target before Thread A proceeds!
vector.add(target); // Results in duplicate entry!
}
To ensure thread safety for compound operations, the calling code must wrap the sequence in an external synchronized block:
synchronized (vector) {
if (!vector.isEmpty()) {
String item = vector.remove(0);
}
}
If developers must manage external synchronization regardless, Vector’s internal per-method monitor locks serve only as redundant performance bottlenecks.
2. Iteration Vulnerability: ConcurrentModificationException
Iterating over a Vector across multiple threads is not inherently safe:
// ❌ THROWS ConcurrentModificationException if another thread mutates the vector!
for (String item : vector) { // Invokes vector.iterator() under the hood
process(item);
}
The iterator() method in Vector returns a standard Fail-Fast iterator that inspects modCount on each step. Because the iterator does not hold the Vector monitor lock throughout the entire duration of the loop, an interleaved write by another thread causes the iterator to immediately abort with ConcurrentModificationException. Multi-threaded iteration requires explicit external synchronization:
synchronized (vector) {
for (String item : vector) {
process(item);
}
}
3. Resizing Differences: $2.0\times$ vs. $1.5\times$
VectorResizing Logic:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);Unless configured with a custom
capacityIncrement,Vectordoubles its buffer size ($2.0\times$), causing aggressive, wasteful heap allocation.ArrayListResizing Logic: Scales by $1.5\times$, optimizing heap buffer reuse and reducing memory footprint.
🔴 Senior Level
1. Lock Contention and Reader-Thread Serialization
In Vector, all read and write methods synchronize on the same intrinsic instance monitor (synchronized (this)):
- If 50 concurrent worker threads execute read-only queries (
vector.get(i)), they cannot execute concurrently. - The threads are forced into a sequential bottleneck at the CPU level, experiencing severe Lock Contention and destroying application throughput.
Modern High-Performance Alternatives
CopyOnWriteArrayList: Eliminates locks entirely for reads (Lock-Free via snapshot iteration). Mutations copy the backing array. Ideal for read-heavy workloads (99% reads, 1% writes) like security filter chains or event subscriber registries.- Concurrent Queues (
ConcurrentLinkedQueue,ArrayBlockingQueue): Utilize CAS (Compare-And-Swap) atomic instructions or fine-grained dual locks (takeLockandputLock) with condition variables for high-throughput concurrency.
2. The Architectural Flaw: Stack extends Vector (LSP Violation)
Java 1.0 introduced java.util.Stack as an inheritance-based extension of Vector:
public class Stack<E> extends Vector<E>.
This design represents a classic violation of the Liskov Substitution Principle (LSP) and the Single Responsibility Principle (SRP):
Stack<String> stack = new Stack<>();
stack.push("Bottom");
stack.push("Top");
// ❌ CATASTROPHIC VIOLATION OF LIFO INVARIANT:
// Because Stack inherits all Vector methods, callers can bypass the stack top!
stack.add(1, "Injected Middle Element"); // Arbitrary mid-list insertion!
stack.remove(0); // Removes element from bottom of stack!
- A stack is not a substitutable subtype of an indexed array; arbitrary positional insertions destroy the fundamental LIFO invariant.
- The Modern Remedy: Always utilize the
Dequeinterface implemented byArrayDeque:Deque<String> stack = new ArrayDeque<>(); stack.push("Item"); String top = stack.pop();
3. Enumeration vs. Iterator
Vector retains the legacy elements() method returning java.util.Enumeration<E>:
| Dimension | Enumeration<E> (JDK 1.0) |
Iterator<E> (JDK 1.2+) |
|---|---|---|
| Method Names | Verbose (hasMoreElements(), nextElement()) |
Concise (hasNext(), next()) |
| Modification Detection | Not Fail-Fast (does not check modCount) |
Fail-Fast (throws ConcurrentModificationException) |
| Element Removal | Unsupported (read-only traversal) | Supported via iterator.remove() |
| Reliability | May loop indefinitely or return corrupted data on concurrent mutation | Fails fast to expose concurrency bugs early |
4 Tricky Questions
1. Why does synchronizing every individual method in Vector fail to make compound operations thread-safe?
Answer: Individual method synchronization guarantees atomicity only for the duration of a single method invocation.
Real-world business logic almost always executes compound operations (Check-Then-Act or Read-Modify-Write patterns), such as:
if (vector.size() > 0) {
String item = vector.get(0);
}
Between the completion of vector.size() and the invocation of vector.get(0), the thread releases the monitor lock on the vector. Another thread can preempt execution and remove the element. When the first thread resumes and invokes get(0), it throws an ArrayIndexOutOfBoundsException.
Because per-method locks cannot span multiple method calls, developers must synchronize the entire composite block externally (synchronized (vector)), rendering the built-in per-method synchronization redundant and inefficient.
2. Why can iterating over a Vector throw ConcurrentModificationException even though Vector is marketed as a thread-safe synchronized collection?
Answer:
Calling vector.iterator() returns an implementation of Iterator that tracks the collection’s modCount structural mutation counter.
While the individual methods of Vector (add, remove, get) are synchronized, the Iterator object itself does not hold the Vector monitor lock continuously across the entire lifecycle of a for-each loop.
If Thread A begins iterating through the vector and Thread B executes vector.add(e) concurrently, Thread B acquires the lock, mutates the array, increments modCount, and releases the lock. When Thread A advances to the next step (it.next()), the iterator detects a discrepancy between its expected mod count and the vector’s actual modCount, immediately throwing a ConcurrentModificationException. Safe multi-threaded iteration over Vector requires holding an explicit external lock on the vector instance for the entire loop.
3. How does java.util.Stack’s inheritance from Vector violate the Liskov Substitution Principle (LSP), and how is this resolved in modern Java?
Answer: The Liskov Substitution Principle dictates that subtypes must preserve the behavioral contracts and invariants of their base types.
By defining Stack<E> extends Vector<E>, Java 1.0 made Stack an “is-a” dynamic array. Consequently, Stack inherits all public indexed manipulation methods from Vector (add(index, element), remove(index), insertElementAt()). This allows client code to insert or remove elements at arbitrary positions in the middle or bottom of the stack, completely breaking the fundamental Last-In-First-Out (LIFO) invariant.
Modern Java resolves this architectural flaw by providing the Deque interface (implemented by ArrayDeque). ArrayDeque enforces LIFO behavior via dedicated stack methods (push, pop, peek) while omitting arbitrary indexed insertion methods, maintaining structural integrity without synchronization overhead.
4. What are the key differences between Enumeration and Iterator in the context of Vector?
Answer:
Enumeration is a Java 1.0 legacy cursor returned by vector.elements(), whereas Iterator was introduced in Java 1.2 as part of the unified JCF.
- Fail-Fast Mechanics:
Iteratoris Fail-Fast. It monitorsmodCountand immediately throwsConcurrentModificationExceptionif the underlying vector is structurally modified during traversal.Enumerationis not Fail-Fast; if the vector is mutated concurrently, it fails silently, risking infinite loops, skipped elements, or corrupted reads. - Mutation Support:
Iteratordefines a saferemove()method to delete the current element during traversal.Enumerationhas no deletion capabilities. - API Ergonomics:
Iteratoruses modern method naming (hasNext()/next()), whereasEnumerationuses verbose naming (hasMoreElements()/nextElement()).
🎯 Interview Cheat Sheet
30-Second Summary
“
Vectoris an obsolete JDK 1.0 predecessor toArrayListwhere every public method is synchronized on the instance monitor (this). While structurally identical toArrayList, its per-method synchronization incurs substantial lock overhead and causes extreme thread contention on parallel reads, while failing to protect compound check-then-act operations without external synchronization. Additionally,Vectordoubles capacity ($2.0\times$) during resizing, accelerating heap fragmentation compared toArrayList’s $1.5\times$ growth. Modern Java usesArrayListfor single-threaded tasks,CopyOnWriteArrayListfor read-heavy concurrency, andArrayDequefor stacks.”
Common Follow-up Questions
- “How do you make an ArrayList thread-safe?” → Use
Collections.synchronizedList(new ArrayList<>()), which decorates methods with synchronization, or useCopyOnWriteArrayListfor read-intensive concurrent workloads. - “Why is
ArrayDequepreferred overVectororStack?” →ArrayDequeuses an unsynchronized resizable circular array, eliminating monitor lock contention and node allocations while strictly preserving FIFO/LIFO semantics. - “Does Vector implement RandomAccess?” → Yes, because it is backed by an array,
VectorimplementsRandomAccessto support constant-time $O(1)$ indexed reads.
Red Flags (DO NOT Say)
- ❌ “Vector is fully thread-safe, so you never need synchronized blocks with it.” (Compound check-then-act operations still require external
synchronizedlocks). - ❌ “Vector is the recommended thread-safe list in modern Java.” (It is an obsolete legacy class; use
CopyOnWriteArrayListor concurrent queues). - ❌ “Iterating through a Vector is thread-safe by default.” (Its iterator is fail-fast and throws
ConcurrentModificationExceptionif modified concurrently).
Related Topics
- What is Stack — LSP architectural breakdown and
ArrayDeque - What is the difference between ArrayList and LinkedList — Array vs pointer performance
- What is CopyOnWriteArrayList — Modern concurrent list implementation
- When to use synchronized collections — Synchronized wrappers vs concurrent collections
- What are fail-fast and fail-safe iterators — Iteration safety analysis