Part 2 — ArrayList Deep Dive¶
ArrayList is one of the most frequently discussed classes in Java interviews. Interviewers often start with basic questions and then progressively explore internal implementation details, performance, and edge cases.
1. What is ArrayList?¶
ArrayList is a resizable array implementation of the List interface.
Characteristics:
- Preserves insertion order.
- Allows duplicate elements.
- Allows multiple
nullvalues. - Provides fast random access.
- Not thread-safe.
Example:
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");
System.out.println(fruits);
// [Apple, Banana, Apple]
2. Class Hierarchy¶
Object
│
AbstractCollection
│
AbstractList
│
ArrayList
Interfaces:
Iterable
│
Collection
│
List
│
ArrayList
3. Internal Data Structure¶
The most important interview fact:
ArrayList is backed by an array of object references.
Simplified version of the JDK implementation:
public class ArrayList<E> {
transient Object[] elementData;
private int size;
}
Where:
elementData→ internal arraysize→ number of elements actually stored
Example:
Capacity = 10
Index
0 1 2 3 4 5 6 7 8 9
A B C _ _ _ _ _ _ _
size = 3
capacity = 10
Interview point: size and capacity are different.
4. Size vs Capacity¶
Suppose:
ArrayList<Integer> list = new ArrayList<>(10);
Initially:
Capacity = 10
Size = 0
After:
list.add(10);
list.add(20);
list.add(30);
Capacity = 10
Size = 3
Interviewers commonly ask:
"How do you get the capacity?"
There is no public API to retrieve an ArrayList's capacity. You can only obtain its size(). Capacity is an implementation detail.
5. Default Capacity¶
This changed in Java 8.
ArrayList<String> list = new ArrayList<>();
Many candidates think it immediately allocates an array of size 10.
Not anymore.
Initially:
elementData = EMPTY_ELEMENTDATA
The internal array is shared and empty.
When the first element is added:
list.add("A");
Java allocates an array with a capacity of 10.
Why?
To avoid unnecessary memory allocation when many empty lists are created but never used.
6. Growth Algorithm¶
Suppose capacity is 10.
Add the 11th element.
A new array is created.
Java grows the capacity by approximately 1.5×.
Formula:
newCapacity = oldCapacity + (oldCapacity >> 1)
Since:
oldCapacity >> 1
means integer division by 2,
Example:
10
↓
15
↓
22
↓
33
↓
49
↓
73
This balances memory usage and the frequency of resizing.
7. What Happens During add()?¶
Case 1: Space available
[A B C _ _ _]
↓
Add D
↓
[A B C D _ _]
Time complexity:
O(1)
Case 2: Array full
Capacity = 3
[A B C]
Adding D:
- Allocate a larger array.
- Copy existing elements.
- Add
D.
Old
[A B C]
↓
New
[A B C D _ _]
Copying is O(n).
Why is add() considered O(1)?¶
Because most insertions do not trigger resizing.
Resizing occurs occasionally.
The average cost across many insertions is amortized O(1).
Interview example¶
Insert 1,000,000 elements.
Only a small number of insertions require copying; the rest are constant time. The total work grows linearly, giving an average constant cost per insertion.
8. Random Access¶
Suppose:
Index
0 1 2 3 4
A B C D E
Need:
list.get(3);
The JVM calculates:
address = base + index × referenceSize
No traversal is needed.
Time complexity:
O(1)
This is why ArrayList is preferred for frequent indexed access.
9. contains()¶
list.contains("C");
Internally:
Compare A
↓
Compare B
↓
Compare C
↓
Found
Java checks each element using equals().
Worst case:
O(n)
10. remove()¶
Suppose:
[A B C D E]
Remove:
list.remove(1);
Result:
[A C D E _]
Elements after the removed index must shift left.
Time complexity:
O(n)
Removing the last element does not require shifting, so it is effectively O(1).
11. Memory Layout¶
ArrayList stores references, not the objects themselves (for object types).
Example:
List<Employee> list = new ArrayList<>();
Memory layout:
ArrayList
elementData
↓
+------+------+------+
| ref1 | ref2 | ref3 |
+------+------+------+
| | |
v v v
Employee Employee Employee
The objects reside elsewhere on the heap.
12. Fail-Fast Iterator and modCount¶
Each structural modification increments an internal counter called modCount.
Example:
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
Iterator<Integer> it = list.iterator();
list.add(30);
it.next(); // throws ConcurrentModificationException
Why?
The iterator stores the expected modification count when it is created.
expectedModCount = 2
After:
list.add(30);
modCount = 3
When next() executes:
expectedModCount != modCount
↓
ConcurrentModificationException
Important: This is called fail-fast behavior. It is a best-effort mechanism for detecting concurrent structural modification; it is not a synchronization mechanism or a thread-safety guarantee.
13. ArrayList vs Vector¶
| Feature | ArrayList | Vector |
|---|---|---|
| Thread-safe | No | Yes (most methods synchronized) |
| Performance | Faster in single-threaded code | Slower due to synchronization |
| Introduced | JDK 1.2 | JDK 1.0 |
| Growth | ~1.5× | Typically doubles (or configurable) |
| Recommended | Yes | Legacy class |
Today, prefer:
ArrayListfor single-threaded use.CopyOnWriteArrayListor other concurrent collections when appropriate for multi-threaded use.
14. ArrayList vs LinkedList¶
| Operation | ArrayList | LinkedList |
|---|---|---|
| Random access | O(1) | O(n) |
| Append | Amortized O(1) | O(1) |
| Insert/Delete at known node | O(n) due to shifting | O(1) |
| Memory overhead | Lower | Higher |
| CPU cache locality | Excellent | Poor |
Despite theoretical advantages of LinkedList, ArrayList often outperforms it in real applications because contiguous memory improves CPU cache utilization.
15. Production Use Cases¶
Use ArrayList when:
- Reading is much more frequent than writing.
- Random access is required.
- Data size is reasonably predictable.
- Order matters.
- Duplicates are allowed.
Examples:
- REST API responses.
- Product catalogs.
- Search results.
- Configuration lists.
- Report generation.
Avoid it when:
- There are frequent insertions/removals in the middle of very large lists.
- Many threads need to modify the list concurrently.
Common Interview Questions¶
Q1. Why is ArrayList faster than LinkedList for get(index)?¶
Because ArrayList uses an array and computes the element's location directly, whereas LinkedList must traverse nodes.
Q2. Why is add() amortized O(1) instead of O(1)?¶
Occasional resizing requires allocating a larger array and copying all elements, making some insertions O(n). Averaged over many insertions, the cost is constant.
Q3. Why does removing from the middle take O(n)?¶
All subsequent elements must be shifted one position to fill the gap.
Q4. What is modCount?¶
An internal counter that tracks structural modifications to detect concurrent modification during iteration.
Q5. Does ArrayList store objects?¶
For object types, it stores references to objects, not the objects themselves.
Q6. Is ArrayList synchronized?¶
No. It is not thread-safe. External synchronization or a concurrent collection is required for safe concurrent modification.
Complexity Summary¶
| Operation | Complexity |
|---|---|
get(index) | O(1) |
set(index) | O(1) |
add(element) | Amortized O(1) |
add(index, element) | O(n) |
remove(index) | O(n) |
contains() | O(n) |
| Iteration | O(n) |
Key Takeaways¶
ArrayListis backed by a dynamically resized array.- It offers O(1) indexed access.
- Appending is amortized O(1) because resizing is infrequent.
- Insertions and removals in the middle are O(n) due to element shifting.
modCountunderpins fail-fast iterators.sizeandcapacityare distinct concepts.- In practice,
ArrayListis usually the defaultListimplementation unless a specific workload justifies another choice.
Next: Part 3 — LinkedList Deep Dive, where we'll examine node structure, head/tail pointers, traversal strategy, deque operations, memory overhead, and why LinkedList is often less efficient than many developers expect.