DevNotes
Support
Collection Framework · Chapter 04

ArrayList

Internal working of the resizable array — size vs capacity, 1.5x growth, amortized O(1) and performance trade-offs.

◷ 7 min read§ 27 sections? 11 interview questions{ } 17 code examples

#Introduction

If there is one Collection class you must master for Java interviews, it is ArrayList.

Interviewers don't just ask how to use ArrayList; they expect you to understand how it works internally, how it grows, why operations have different time complexities, and when it should or should not be used.

#What is ArrayList?

ArrayList is a resizable array implementation of the List interface.

Package:

Java
java.util.ArrayList

Declaration:

Java
public class ArrayList<E>
        extends AbstractList<E>
        implements List<E>,
                   RandomAccess,
                   Cloneable,
                   Serializable
Iterable
Collection
List
ArrayList
Where ArrayList fits

#Characteristics of ArrayList

  • Maintains insertion order
  • Allows duplicate elements
  • Allows multiple null values
  • Index-based access
  • Dynamically grows
  • Not synchronized
  • Not thread-safe

#Internal Structure

Many beginners think ArrayList is some magical data structure. It is not. Internally it is simply an array of objects.

[0]
A
[1]
B
[2]
C
[3]
D
[4]
E
Index → element

Internally Java stores elements in:

Java
transient Object[] elementData;

This single array is the heart of ArrayList.

#Creating an ArrayList

Java
ArrayList<String> list = new ArrayList<>();

What actually happens? Java creates an empty ArrayList.

Default Constructor

Internally:

Java
elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

Initially Size = 0 and Capacity = 0. The internal array is shared and empty.

What Happens on the First add()?

Java
list.add("Java");

Now Java creates the first internal array. The default capacity becomes 10.

[0]
Java
[1]
null
[2]
null
[3]
null
[4]
null
[5]
null
[6]
null
[7]
null
[8]
null
[9]
null
Capacity = 10, Size = 1

#Size vs Capacity

This is one of the favourite interview questions.

Size

The number of actual elements.

Java
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);

Size = 2.

Capacity

The total number of elements that can be stored before resizing. Current capacity = 10.

Capacity Example

StepCapacitySize
new ArrayList<>()00
After first insertion101
After adding 10 elements1010
After adding the 11th elementgrows (15)11

#Growth Algorithm

Interview favourite. Suppose the current capacity is 10 and we need one more element. Java creates a new capacity of 15.

Text
newCapacity = oldCapacity + (oldCapacity >> 1)

Meaning: old + 50%.

10
15
22
33
49
73
109
Growth is approximately 1.5×, not double

Why Not Double the Size?

Imagine storing 10 million objects. Doubling on every resize would waste large amounts of memory. Using 1.5× growth balances:

  • Memory usage
  • Performance
  • Fewer reallocations

#What Happens During Resize?

Suppose capacity = 10 and you insert the 11th element.

Old array fullcapacity 10: [1 … 10]
Create a new larger arraycapacity 15
Copy all existing elements
Insert the new element
Old arrayeligible for Garbage Collection

Resize Cost

This copy operation is expensive, because all elements are copied again. Complexity: O(n).

#Why is add() O(1)?

Interview favourite. Most insertions occur at the end:

Java
list.add(100);

Java simply places the element in the next free slot → O(1). Only during resizing does it cost O(n). Therefore the average complexity is amortized O(1).

#Amortized Time Complexity

#ensureCapacity()

Sometimes we already know how many elements we need.

Java
ArrayList<Employee> list = new ArrayList<>();
list.ensureCapacity(10000);

Benefits:

  • Reduces resizing
  • Improves performance
  • Fewer array copies

Useful when loading large datasets from files or databases.

#trimToSize()

Suppose capacity = 1000 but size = 50. Extra memory is wasted. Use:

Java
list.trimToSize();

Now capacity = 50. Memory optimized.

#Accessing Elements

Java
list.get(5);

How? Arrays support direct indexing:

Text
Address + (Index × Element Size)

Therefore O(1). Random access is extremely fast.

#Why Does ArrayList Implement RandomAccess?

Notice implements RandomAccess. RandomAccess is a marker interface — it contains no methods. It tells Java:

This collection supports fast random access.

Algorithms can optimize based on this information.

#Insertion

Adding at the end — list.add(100); → O(1).

Adding at the beginning — list.add(0, 100);

A B C D
100 A B C D
Every element shifts one position: O(n)

#Removal

Removing the last element — list.remove(list.size() - 1); → O(1).

Removing the first element — list.remove(0); — the remaining elements shift left.

A B C D
B C D
O(n)

#contains()

Java
list.contains("Java");

Java searches sequentially: element 1 → element 2 → element 3 … Complexity: O(n).

#Iteration

Java
for (String s : list)

Complexity O(n), but fast because memory is contiguous. This makes ArrayList more CPU cache-friendly than LinkedList.

#Null Values and Duplicates

ArrayList allows multiple null values:

Java
list.add(null);
list.add(null);

Duplicates are allowed:

Java
list.add("Java");
list.add("Java");
Output
[Java, Java]

#Thread Safety

How to Make It Thread-Safe?

Option 1:

Java
List<String> list = Collections.synchronizedList(new ArrayList<>());

Option 2: CopyOnWriteArrayList (covered later).

#ArrayList vs Array

ArrayArrayList
Fixed sizeDynamic
Stores primitives & objectsStores objects (generics use wrapper types for primitives)
FasterSlightly slower due to abstraction
No rich APIRich Collection API

#ArrayList vs LinkedList

FeatureArrayListLinkedList
Internal StructureDynamic ArrayDoubly Linked List
Random AccessO(1)O(n)
Insert at EndO(1)*O(1)
Insert at BeginningO(n)O(1) (after locating node)
Remove by IndexO(n)O(n)
Memory UsageLowerHigher (node references)
Cache LocalityExcellentPoor

*Amortized O(1)

#Common Mistakes

  • Using raw types: ArrayList list = new ArrayList(); — prefer generics: ArrayList<String> list = new ArrayList<>();
  • Creating a huge ArrayList without pre-sizing — better: new ArrayList<>(100000); or list.ensureCapacity(100000);
  • Using ArrayList for frequent insertions at the beginning — a better choice is LinkedList (or ArrayDeque).

#Internal Source Code (Simplified)

Java
public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

A resize happens only if required. Otherwise, the element is simply placed into the next free slot.

#Time Complexity

OperationComplexity
get(index)O(1)
set(index)O(1)
add(end)O(1) amortized
add(index)O(n)
remove(last)O(1)
remove(index)O(n)
contains()O(n)
indexOf()O(n)
iterationO(n)
clear()O(n)

#Frequently Asked Interview Questions

Q1How does ArrayList work internally?

It stores elements in a dynamically resizable Object[] array.

Q2Difference between size and capacity?
  • Size = number of actual elements.
  • Capacity = size of the internal array.
Q3What is the default capacity?

With the default constructor, the internal array is initially empty. On the first insertion, the capacity grows to 10.

Q4What is the growth formula?

Approximately newCapacity = oldCapacity + (oldCapacity >> 1) — roughly 1.5× the old capacity.

Q5Why is add() O(1)?

Most insertions simply place the element into the next available slot. Only occasional resizing requires copying elements, making the average cost amortized O(1).

Q6Is ArrayList synchronized?

No.

Q7Why does ArrayList implement RandomAccess?

To indicate that it supports efficient random (index-based) access, allowing algorithms to optimize accordingly.

Q8When should you use ensureCapacity()?

When you know approximately how many elements you'll store, to reduce resizing and improve performance.

Q9What does trimToSize() do?

It shrinks the internal capacity to match the current size, reducing unused memory.

#Chapter Summary

  • ArrayList is backed by a dynamically resizable Object[].
  • It provides O(1) random access through array indexing.
  • Capacity and size are different concepts.
  • Capacity grows by approximately 1.5× when required.
  • add() is amortized O(1) because resizing happens infrequently.
  • ensureCapacity() improves bulk insertion performance.
  • trimToSize() helps reclaim unused memory.
  • ArrayList is not thread-safe.