Home » Java Tutorial » Java ArrayList – Internal Working and Examples

Java ArrayList – Internal Working and Examples

In Java, we often need to store a group of elements. Arrays can be used for this, but they have fixed size, and managing them can be tricky when data keeps changing.

That’s where Collections Framework comes in. And one of the most popular and frequently used classes from this framework is ArrayList.

ArrayList is one of the most commonly used classes in the Java Collection Framework. It provides a dynamic array that grows as needed, making it a powerful tool for developers building scalable Java applications.

This tutorial is designed like a classroom lecture, starting from the basics and gradually diving into all aspects of ArrayList with examples and detailed explanations.

What is ArrayList in Java?

ArrayList is a resizable array implementation of the List interface. Unlike arrays in Java, which are of fixed size, an ArrayList can grow and shrink at runtime as elements are added or removed.

import java.util.ArrayList;

public class Demo {
    public static void main(String[] args) {
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Mango");
        System.out.println(fruits); // Output: [Apple, Mango]
    }
}

Key Features of ArrayList

  • Dynamic size: Grows automatically when needed.
  • Ordered: Maintains the insertion order.
  • Allows duplicates: You can add the same element multiple times.
  • Indexed: Supports random access using index (like an array).
  • Non-synchronized: Not thread-safe by default.

When to use ArrayList?

Use ArrayList when:

  • You need a resizable array.
  • You want fast random access to elements using an index.
  • You are working in a single-threaded environment or using external synchronization for threads.

Internal Implementation of ArrayList

➤ Backed by Array

Internally, ArrayList uses a dynamic array (i.e., Object[] array). When you create an ArrayList, it initializes an empty array with a default capacity (usually 10).

transient Object[] elementData; // array buffer

➤ Capacity vs Size

  • Size: Number of elements currently in the list.
  • Capacity: Length of the internal array.

➤ Growth Strategy

When the internal array becomes full, the ArrayList increases its capacity by 1.5 times the current size:

newCapacity = oldCapacity + (oldCapacity >> 1);

This strategy balances performance and memory overhead.

How ArrayList Operations Works Internally

Add Operation

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
  • Checks capacity
  • Resizes array if needed
  • Adds the element at size index
  • Increments size

Get Operation

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
  • Performs index bounds check
  • Returns element at given index

Remove Operation

public E remove(int index) {
    rangeCheck(index);
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // free memory
    return oldValue;
}
  • Shifts remaining elements left by one
  • Reduces size
  • Frees memory (prevents memory leak)

Example: Basic Operations

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> animals = new ArrayList<>();

        animals.add("Dog");
        animals.add("Cat");
        animals.add("Horse");

        System.out.println("Animals: " + animals); // [Dog, Cat, Horse]

        animals.set(1, "Elephant"); // Replace Cat with Elephant
        System.out.println("After set: " + animals); // [Dog, Elephant, Horse]

        animals.remove("Dog");
        System.out.println("After remove: " + animals); // [Elephant, Horse]

        System.out.println("Size: " + animals.size()); // 2

        System.out.println("Contains Horse? " + animals.contains("Horse")); // true
    }
}

Iterating through an ArrayList

There are several ways to loop through an ArrayList:

1. Using for-each loop

for (String animal : animals) {
    System.out.println(animal);
}

2. Using for loop with index

for (int i = 0; i < animals.size(); i++) {
    System.out.println(animals.get(i));
}

3. Using Iterator

import java.util.Iterator;

Iterator<String> it = animals.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

Generics with ArrayList

Without Generics (Not type-safe)

ArrayList list = new ArrayList();
list.add("Apple");
list.add(10); // No error at compile time

String fruit = (String) list.get(0); // Requires type casting

With Generics (Type-safe)

ArrayList<String> list = new ArrayList<>();
list.add("Apple");
// list.add(10); // ❌ Compile time error

String fruit = list.get(0); // No casting needed

Conversion Between Array and ArrayList

Array to ArrayList

String[] array = {"A", "B", "C"};
ArrayList<String> list = new ArrayList<>(Arrays.asList(array));

ArrayList to Array

ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");

String[] array = list.toArray(new String[0]);

Thread Safety

ArrayList is not synchronized.

To make it thread-safe:

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

Or use alternatives like CopyOnWriteArrayList.

ArrayList Example Program

import java.util.*;

public class Example {
    public static void main(String[] args) {
        ArrayList<Integer> nums = new ArrayList<>();
        nums.add(10);
        nums.add(20);
        nums.add(30);
        
        for (int num : nums) {
            System.out.println(num);
        }
    }
}

Explanation of above program:

  • import java.util.*;: This line imports the entire java.util package, which includes the ArrayList class.
  • ArrayList<Integer> nums = new ArrayList<>();: Creates an empty ArrayList that stores Integer values (not primitive int).
  • nums.add(10);, nums.add(20);, nums.add(30);: Adds values to the list dynamically.
  • for (int num : nums): Uses an enhanced for-loop to iterate through the list.
  • System.out.println(num);: Prints each number in the list one by one.

Output:

10
20
30

This is the most basic and clear example of how an ArrayList works.

1. Insert at Specific Index

nums.add(1, 15); // Inserts 15 at index 1

Explanation:

  • add(index, element) inserts the specified element at the given index.
  • This shifts the existing elements (and their indices) to the right.
  • In our case, 15 is inserted at index 1 (between 10 and 20).

Before Insertion: [10, 20, 30]
After Insertion: [10, 15, 20, 30]

Note: If the index is greater than size(), Java will throw IndexOutOfBoundsException.

2. Remove Elements by Index

nums.remove(2); // Removes element at index 2

Explanation:

  • This removes the element present at index 2 of the list.
  • After removing, all elements after that index shift one place to the left.

Before Removal: [10, 15, 20, 30]
After Removal: [10, 15, 30]

Removing by index is different from removing by object (nums.remove(Integer.valueOf(30));).

3. Sorting an ArrayList

Collections.sort(nums); // Ascending order

Explanation:

  • This uses the Collections.sort() method to sort the elements of the list in ascending order.
  • It sorts the list in-place, meaning it modifies the original list.

Before Sorting: [30, 10, 20]
After Sorting: [10, 20, 30]

To sort in descending order, use:

nums.sort(Collections.reverseOrder());

Summary of What You Learned in Above Example

OperationMethod UsedDescription
Addadd(element)Adds at the end
Add at indexadd(index, element)Inserts at specified index
Removeremove(index)Removes the element at the index
SortCollections.sort(list)Sorts list in ascending order

Time and Space Complexity of some ArrayList operations

OperationTime ComplexityExplanation
add()O(1) amortizedO(1) unless resize is needed
add(index)O(n)May require shifting elements
remove(index)O(n)Shifting needed
get(index)O(1)Direct access
contains()O(n)Needs to scan elements
size()O(1)Direct field access

Common Pitfalls

  • IndexOutOfBoundsException: Accessing invalid index.
  • ConcurrentModificationException: When modifying list while iterating using enhanced for-loop.
for (int i : list) {
    list.remove(0); // throws exception
}

Fix using Iterator:

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    it.next();
    it.remove(); // safe removal
}

Best Practices

  • Use ArrayList when frequent access is needed.
  • For frequent insertions/deletions in middle, prefer LinkedList.
  • Initialize with expected size if known: javaCopyEditArrayList<String> list = new ArrayList<>(100);

ArrayList Methods in Java with Examples

MethodDescriptionVersion
add(E e)Adds the element to the end of the list.✅ Java 1.2+
add(int index, E element)Inserts the element at the specified index.✅ Java 1.2+
addAll(Collection<? extends E> c)Adds all elements from the specified collection.✅ Java 1.2+
addAll(int index, Collection<?> c)Inserts all elements from the collection at the given index.✅ Java 1.2+
get(int index)Returns the element at the specified index.✅ Java 1.2+
set(int index, E element)Replaces the element at the specified position.✅ Java 1.2+
remove(int index)Removes the element at the specified index.✅ Java 1.2+
remove(Object o)Removes the first occurrence of the specified element.✅ Java 1.2+
clear()Removes all elements from the list.✅ Java 1.2+
size()Returns the number of elements in the list.✅ Java 1.2+
isEmpty()Checks if the list is empty.✅ Java 1.2+
contains(Object o)Checks if the list contains the specified element.✅ Java 1.2+
indexOf(Object o)Returns the index of the first occurrence of the specified element.✅ Java 1.2+
lastIndexOf(Object o)Returns the index of the last occurrence.✅ Java 1.2+
toArray()Returns an array containing all elements.✅ Java 1.2+
toArray(T[] a)Returns an array of the specified type.✅ Java 1.2+
ensureCapacity(int minCapacity)Ensures the list has at least the specified capacity.✅ Java 1.4+
trimToSize()Trims capacity to current size.✅ Java 1.2+
clone()Returns a shallow copy of the list.✅ Java 1.2+
iterator()Returns an iterator over the list.✅ Java 1.2+
listIterator()Returns a list iterator for forward/backward traversal.✅ Java 1.2+
subList(int fromIndex, int toIndex)Returns a view of the portion between given indexes.✅ Java 1.2+
retainAll(Collection<?> c)Retains only elements also in the specified collection.✅ Java 1.2+
removeAll(Collection<?> c)Removes all elements present in the specified collection.✅ Java 1.2+
equals(Object o)Compares the list to another object for equality.✅ Java 1.2+
hashCode()Returns the hash code of the list.✅ Java 1.2+
forEach(Consumer<? super T> action)Performs the given action for each element.🟨 Java 8+
sort(Comparator<? super E> c)Sorts the list using a custom comparator.🟨 Java 8+
removeIf(Predicate<? super E> filter)Removes elements that match a given condition.🟨 Java 8+
replaceAll(UnaryOperator<E> operator)Replaces each element with the result of applying the operator.🟨 Java 8+
spliterator()Creates a Spliterator over the elements in the list (used for parallelism).🟨 Java 8+

Read the Full Article: ArrayList Methods in Java

🆚 ArrayList vs Array

FeatureArrayArrayList
SizeFixedDynamic
TypeCan be primitivesOnly objects
PerformanceSlightly fasterMore flexible
MethodsNo built-in methodsMany utility methods

Real-World Use Cases

  • Maintaining a dynamic list of records (e.g., shopping cart).
  • Temporary storage when element order matters.
  • Caching and lookups where fast random access is needed.

Frequently Asked Questions (FAQs) on ArrayList in Java

Q1. What is the difference between an Array and an ArrayList in Java?

Answer:

  • An Array is a fixed-size data structure that can hold both primitive and object types.
  • An ArrayList is a dynamic array that can grow or shrink in size and only holds object references.
  • Arrays use less memory and are slightly faster, but ArrayLists provide more flexibility and built-in methods.

Q2. Is ArrayList synchronized in Java?

Answer:
No, ArrayList is not synchronized by default. It is not thread-safe for concurrent access. To make it synchronized:

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

For thread-safe operations in multi-threaded environments, you can also use CopyOnWriteArrayList.

Q3. What is the initial capacity of an ArrayList?

Answer:
The default capacity of an ArrayList when created with the default constructor is 10. You can specify a custom initial capacity using:

ArrayList<Integer> list = new ArrayList<>(50);

Q4. How does ArrayList grow internally?

Answer:
When the ArrayList exceeds its current capacity, it resizes its internal array by increasing the size by 1.5 times (i.e., newCapacity = oldCapacity + oldCapacity / 2).

Q5. Can we store null in an ArrayList?

Answer:
Yes, ArrayList can store null values. You can add multiple null entries as it allows duplicates.

ArrayList<String> list = new ArrayList<>();
list.add(null);
list.add(null);
System.out.println(list); // [null, null]

Q6. How to sort an ArrayList?

Answer:
You can use Collections.sort() for ascending order:

Collections.sort(list);

Or use a custom comparator for descending:

list.sort(Collections.reverseOrder());

Q7. What is the time complexity of ArrayList operations?

Answer:

OperationTime Complexity
Add (end)O(1) (amortized)
Add (middle)O(n)
Remove by indexO(n)
Get by indexO(1)
Search (contains)O(n)

Q8. How do you remove an element while iterating over an ArrayList?

Answer:
Use an Iterator to safely remove elements during iteration:

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() < 10) {
        it.remove();
    }
}

Avoid using for-each loop for removal, as it may throw ConcurrentModificationException.

Q9. Can we make ArrayList read-only?

Answer:
Yes, by using:

List<String> readOnlyList = Collections.unmodifiableList(list);

Any attempt to modify readOnlyList will throw UnsupportedOperationException.