ArrayList in Java

ArrayList is one of the most widely used classes in the Java Collections Framework. It is a resizable array implementation of the List interface, which means it stores elements in order, allows duplicate values, supports index-based access, and grows dynamically as elements are added. For many everyday Java programs, ArrayList is the default list implementation because it is simple, flexible, and efficient for reading and iterating data.

The main idea behind ArrayList is convenience over a plain array. A normal array has a fixed length. Once an array is created, its size cannot change. ArrayList solves this limitation by managing a resizable internal array. Developers can add and remove elements without manually creating a new array each time the data grows or shrinks. This makes it practical for real applications where the number of values is not always known upfront.

ArrayList is also a high-frequency interview topic because it connects several core Java ideas: collections, generics, arrays, dynamic resizing, time complexity, duplicate handling, insertion order, iteration, sorting, thread safety, and the difference between removing by index and removing by object. A strong understanding of ArrayList helps developers write cleaner code and choose the right data structure for common problems.

ArrayList

What Is ArrayList?

ArrayList is a class in the java.util package that implements the List interface. Since it implements List, it behaves like an ordered sequence of elements. Elements can be accessed using indexes, and the first element is stored at index 0. This index-based model makes ArrayList familiar to developers who already understand arrays.

Unlike arrays, however, an ArrayList does not require the developer to decide the final size at creation time. It can grow when more elements are added. Internally, it still uses an array-like structure, but it handles resizing automatically. From the developer's point of view, adding values is straightforward through methods such as add, set, get, and remove.

ArrayList allows duplicate values because each value has its own position. It also maintains insertion order, so values are normally returned in the same order in which they were added. Common implementations allow multiple null values as well, although null handling should be intentional in real business code.

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

Position in Collection Hierarchy

ArrayList sits under the List branch of the Java Collections Framework. It inherits general collection behavior from Collection and iteration capability from Iterable. This is why an ArrayList can be used in enhanced for-each loops and can also be referenced by the more general List interface.

Iterable
   |-- Collection
        |-- List
             |-- ArrayList
          

In real code, it is common to declare variables using the interface type and instantiate the implementation: List<String> names = new ArrayList<>();. This keeps the code flexible because the variable depends on list behavior rather than one specific implementation.

Key Characteristics of ArrayList

The most important characteristic of ArrayList is that it is backed by a dynamic array. This gives it fast random access. When code calls get(5), the list can reach that position directly. This makes ArrayList efficient for read-heavy operations, index-based access, and normal sequential iteration.

Another important characteristic is automatic growth. When the internal array becomes full and a new element is added, ArrayList creates a larger internal array and copies existing elements into it. This resizing is handled internally, so the developer does not manually manage array length. Most additions at the end are efficient, although occasional resizing creates extra work.

The tradeoff appears when elements are inserted or removed from the middle. Since elements are stored sequentially in an internal array, elements after the insertion or removal point may need to shift. This makes middle insertions and deletions more expensive than simple reads or appends. ArrayList is also not synchronized, so it is not automatically safe for concurrent modification by multiple threads.

How ArrayList Works Internally

Internally, ArrayList uses an array to store elements. Conceptually, it behaves like an array that can expand. The list keeps track of how many elements are actually stored, while the internal array may have extra capacity available for future additions. The number of stored elements is the size. The length of the internal storage is the capacity.

When the size reaches the current capacity and another element is added, the list grows by creating a larger internal array and copying existing elements into it. This is why adding to the end is described as amortized constant time. Most additions are cheap, but occasional additions trigger resizing and copying. Spread across many additions, the average cost remains efficient.

Older explanations often say the default capacity is 10. In modern Java implementations, the actual internal allocation details are optimized, but the interview-level idea remains the same: ArrayList uses internal array storage, has capacity separate from size, and grows automatically when needed. The important practical point is that resizing has a cost, so providing an initial capacity can help when the expected size is known.

Creating an ArrayList

An ArrayList can be created in several ways. The most common way is using the default constructor. This creates an empty list and lets it grow as elements are added. This is suitable when the expected number of elements is small or unknown.

1. Default Constructor

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

2. With Initial Capacity

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

Providing an initial capacity can improve performance when the approximate size is known. For example, if an application expects to load 50,000 records, creating the list with enough capacity can reduce repeated resizing. This does not set the list size; it only prepares internal storage.

3. From Another Collection

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

Creating an ArrayList from another collection is useful when a mutable list copy is needed. For example, Arrays.asList returns a fixed-size list, but wrapping it in a new ArrayList creates a list where elements can be added and removed.

Commonly Used Methods

ArrayList provides common list operations for adding, accessing, updating, removing, and searching elements. These methods come from the List and Collection contracts and are implemented by ArrayList.

Adding Elements

list.add(10);
list.add(1, 20);
          

Accessing Elements

int value = list.get(0);
          

Updating Elements

list.set(0, 100);
          

Removing Elements

list.remove(0);       // by index
list.remove(Integer.valueOf(10)); // by value
          

Size and Search

list.size();
list.contains(20);
list.indexOf(20);
          

The contains and indexOf methods search through elements. Because ArrayList is not a hash-based structure, searching by value usually requires scanning elements one by one. If frequent membership checks are the main requirement, a HashSet may be more suitable.

Iterating an ArrayList

Iteration is one of the most common operations on an ArrayList. A for-each loop is simple and readable when the code only needs to read elements. An Iterator is useful when elements need to be removed safely during traversal. A ListIterator supports movement in both directions and can also work with positions.

Using for-each loop

for (Integer i : list) {
    System.out.println(i);
}
          

Using Iterator

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

Using ListIterator

ListIterator<Integer> li = list.listIterator();
          

Performance Complexity (Important)

Performance is one of the main reasons ArrayList is popular. Accessing and updating by index are fast because the internal array allows direct positional access. Adding at the end is usually fast, although occasional resizing may occur. Insertions and removals in the middle are slower because elements may need to shift.

Operation Time Complexity
get/set O(1)
add (end) O(1) amortized
add (middle) O(n)
remove (middle) O(n)
search O(n)

ArrayList vs Array (Quick Comparison)

Arrays and ArrayList are closely related but not the same. An array is fixed in size and can store primitives or objects. An ArrayList is dynamic and stores objects, so primitive values are stored through wrapper classes such as Integer, Double, and Boolean. Autoboxing makes this convenient, but the distinction matters.

Aspect Array ArrayList
Size Fixed Dynamic
Type Primitive + Object Object only
Methods Limited Rich API
Performance Faster Slight overhead

Arrays are useful when size is fixed and maximum performance or primitive storage is important. ArrayList is better when the number of elements changes, when list methods are useful, or when the code benefits from the Collections Framework API. Most business application code uses ArrayList more often than raw arrays for dynamic groups of objects.

Thread Safety

ArrayList is not synchronized. If multiple threads modify the same ArrayList at the same time without coordination, the result can be unpredictable. The list may produce inconsistent data, runtime exceptions, or corrupted internal state. In single-threaded usage, this is not a concern. In concurrent applications, it must be handled deliberately.

Thread-safe alternatives include Collections.synchronizedList and CopyOnWriteArrayList. A synchronized list wraps a normal list with synchronized access. CopyOnWriteArrayList is useful for cases with many reads and few writes because modifications create a new copy internally. The right choice depends on the access pattern.

ArrayList and Generics

ArrayList is almost always used with generics. An ArrayList<String> stores strings, while an ArrayList<Integer> stores integer objects. Generics allow the compiler to catch incorrect element types early and reduce the need for casting when retrieving elements.

Using raw ArrayList without generics is a common beginner mistake. Raw lists can accept mixed object types, which may lead to runtime casting errors. Generics make code safer and more readable because the intended element type is visible in the declaration.

ArrayList and Null Values

ArrayList can store null values, including multiple null values. Since it allows duplicates and stores elements by position, null can appear at different indexes. This can be useful in some cases, but it can also create NullPointerException risks if the code assumes every element is non-null.

In real applications, null values should be handled intentionally. If null is not a valid business value, it should be filtered out or rejected before being added. If null represents missing information, later code should check for it before calling methods on the element.

When to Use ArrayList

Use ArrayList when order matters, duplicates are allowed, fast index-based access is needed, and most operations involve reading, appending, or iterating. It is a strong default for storing results, test data, records, menu items, report rows, messages, and sequential values.

Avoid ArrayList as the first choice when frequent insertions or deletions happen near the beginning or middle of a large list, when uniqueness is required, or when many threads modify the same list. In those cases, another collection such as LinkedList, HashSet, or a concurrent collection may fit better.

ArrayList in Real Projects

ArrayList appears throughout real Java projects. A service may return an ArrayList or List of users. A test automation framework may store a list of page elements, validation messages, or test data rows. A reporting module may collect rows before writing output. A backend API may build a list of response objects before returning JSON.

In professional code, developers often declare variables and method return types as List while using ArrayList as the implementation. This communicates that callers should depend on list behavior rather than the specific implementation. It also allows the implementation to change later if needed.

ArrayList and Safe Removal

Removing elements while iterating is a common source of bugs. If code uses a for-each loop and directly calls list.remove, Java may throw a ConcurrentModificationException. The safe approach is to use an Iterator and call iterator.remove, or to create a separate list of items to remove after iteration.

This matters in real applications because filtering and cleanup logic often removes values based on a condition. For example, a test may remove invalid data rows, a service may remove inactive records, or a report may filter entries. Safe removal keeps this logic stable.

Size vs Capacity

One of the most useful internal distinctions in ArrayList is the difference between size and capacity. Size means the number of elements currently stored in the list. Capacity means the amount of internal storage currently available before another resize is needed. The size() method returns the number of actual elements, not the capacity.

For example, an ArrayList may have capacity for many elements but still have size zero if no elements have been added. When elements are added, size increases. When the list grows beyond current capacity, the internal storage expands. This growth is automatic, but it is not free because the old elements must be copied into a larger internal array.

The ensureCapacity method can be used when the expected number of elements is known. It prepares enough internal storage in advance and reduces repeated resizing. This is useful when loading large data sets, processing files, collecting database records, or building large result lists. It is not necessary for small lists, but it shows practical understanding of how ArrayList manages memory.

remove(int) vs remove(Object)

The overloaded remove method is one of the most common ArrayList interview traps. When the list contains objects such as strings, the difference is usually obvious. remove(0) removes the element at index zero, while remove("A") removes the first matching value. With integer lists, the distinction becomes more subtle.

In a List<Integer>, calling remove(1) removes the element at index 1, not the value 1. To remove the value, the code should use remove(Integer.valueOf(1)). This happens because Java chooses the remove(int) overload when an int literal is provided. Understanding this behavior prevents accidental removal of the wrong element.

This issue is not just academic. A production bug can occur if a developer intends to remove a value from a list of IDs but accidentally removes by index. The code may compile and run, but the wrong data is removed. Clear method usage and tests around removal logic are important when working with integer lists.

Sorting an ArrayList

Sorting is a common operation on ArrayList because it preserves order. The list can be sorted using Collections.sort or the list's sort method. Natural sorting works when the elements implement Comparable, such as integers and strings. Custom sorting uses a Comparator to define the ordering rule.

For business objects, sorting usually depends on a specific field. A list of users may be sorted by name, age, registration date, or account status. A list of products may be sorted by price, rating, or availability. A list of test results may be sorted by execution time or severity. A comparator makes these rules explicit.

Sorting modifies a mutable list in place. If the original order is important, create a copy before sorting. This matters when insertion order has business meaning, such as user-selected priority, original file order, or search ranking. A clean implementation decides whether sorting should mutate the existing list or produce a sorted copy.

Converting Between Arrays and ArrayList

Conversion between arrays and ArrayList is common because Java APIs sometimes use arrays while application logic often prefers collections. Arrays.asList creates a list view backed by an array. This list has fixed size, so elements can be replaced, but elements cannot be added or removed. This is a common source of UnsupportedOperationException.

When a fully mutable list is required, wrap the result in a new ArrayList. For example, new ArrayList<>(Arrays.asList(arr)) creates a separate mutable list. This allows normal add and remove operations. Understanding this difference helps avoid bugs when converting array data into a list for further processing.

Converting an ArrayList back to an array can be done with toArray. The typed form, such as list.toArray(new String[0]), is usually more useful than receiving an Object[] because it preserves the expected array type. This is useful when calling APIs that require arrays.

ArrayList and API Design

In method signatures, it is often better to use List instead of ArrayList unless the method specifically requires ArrayList behavior. For example, a service method can return List<Order> even if it internally builds an ArrayList. This gives callers the behavior contract without forcing them to depend on one concrete class.

The same idea applies to parameters. If a method only needs ordered list behavior, accept List<String> instead of ArrayList<String>. This allows callers to pass different list implementations. It also makes the method easier to test and reuse. Programming to interfaces is a strong design habit supported by the Collections Framework.

API design should also consider mutability. Returning an internal mutable ArrayList directly can allow outside code to change an object's internal state. If the caller should only read the data, return an unmodifiable list or a defensive copy. This connects ArrayList usage with encapsulation and safe object design.

ArrayList in Coding Interviews

ArrayList appears frequently in coding interviews because it is flexible and easy to use for ordered results. It is commonly used to store output, collect matching elements, build dynamic sequences, convert arrays, sort values, and return answers from methods. Many array problems can be adapted into ArrayList problems when the result size is not known in advance.

Common patterns include adding elements conditionally, removing invalid entries with an iterator, sorting before processing, checking size, accessing neighboring elements by index, and converting between array and list forms. Interviewers may also ask why ArrayList is better than an array in certain cases and why it is weaker than a set for uniqueness checks.

A strong interview answer explains both behavior and tradeoff. ArrayList is ordered, dynamic, duplicate-friendly, and fast for index access. It is not synchronized, not ideal for frequent middle insertion or deletion, and not designed for uniqueness enforcement. That balanced explanation is stronger than simply saying it is a dynamic array.

Common Beginner Mistakes

Beginners often use ArrayList in a multi-threaded context without synchronization. This can be unsafe if multiple threads modify the list. Another common mistake is removing elements incorrectly during iteration, which can cause runtime exceptions. Safe removal should use an iterator or a separate filtering approach.

Another frequent issue is forgetting generics and using raw ArrayList. This weakens type safety. Beginners also use ArrayList for frequent middle insertions, even though shifting elements can make that inefficient. Choosing a collection should always depend on the dominant operation.

Interview Perspective

In interviews, a strong answer should define ArrayList as a resizable array implementation of the List interface. It should mention that it maintains insertion order, allows duplicates, allows null values, provides fast random access, and is not synchronized. These are the core facts interviewers expect.

A stronger answer explains internal behavior. ArrayList uses an internal array, grows when capacity is exceeded, and copies elements during resizing. Access by index is fast, adding at the end is usually efficient, and adding or removing in the middle may require shifting elements. Mentioning remove(int) versus remove(Object) is also useful because it is a common interview trap.

Interview-Ready Answers

Short Answer

ArrayList is a resizable array implementation of the List interface.

Detailed Answer

In Java, ArrayList is a dynamic array-based implementation of the List interface. It allows duplicates, maintains insertion order, and provides fast random access. It is not synchronized and is best suited for scenarios with frequent read, append, and iteration operations.

ArrayList Examples (Top Interview Patterns)

1. Creating an ArrayList

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Selenium");
        System.out.println(list);
    }
}
          

Output

[Java, Selenium]
          

2. ArrayList Allows Duplicate Values

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(10);
        System.out.println(list);
    }
}
          

Output

[10, 10]
          

3. Maintains Insertion Order

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(1);
        list.add(2);
        System.out.println(list);
    }
}
          

Output

[3, 1, 2]
          

4. Accessing Elements by Index

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        System.out.println(list.get(1));
    }
}
          

Output

B
          

5. Modifying Elements using set()

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "API"));
        list.set(1, "Selenium");
        System.out.println(list);
    }
}
          

Output

[Java, Selenium]
          

6. Removing Element by Index

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        list.remove(0);
        System.out.println(list);
    }
}
          

Output

[B, C]
          

7. Removing Element by Object

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        list.remove("B");
        System.out.println(list);
    }
}
          

Output

[A, C]
          

8. remove(int) vs remove(Object) (Very Important)

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(10, 20, 30));
        list.remove(1);                  // removes index 1
        // list.remove(Integer.valueOf(20)); // removes value
        System.out.println(list);
    }
}
          

Output

[10, 30]
          

9. Iterating Using for-each Loop

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("X", "Y", "Z"));
        for (String s : list) {
            System.out.println(s);
        }
    }
}
          

10. Iterating Using Iterator

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}
          

11. Removing While Iterating (Correct Way)

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> it = list.iterator();
        while (it.hasNext()) {
            if (it.next() == 2) {
                it.remove();
            }
        }
        System.out.println(list);
    }
}
          

Output

[1, 3]
          

12. ArrayList Size

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B"));
        System.out.println(list.size());
    }
}
          

Output

2
          

13. Checking Element Existence

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "API"));
        System.out.println(list.contains("Java"));
    }
}
          

Output

true
          

14. Sorting an ArrayList

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
        Collections.sort(list);
        System.out.println(list);
    }
}
          

Output

[1, 2, 3]
          

15. Custom Sorting using Comparator

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "API", "Test"));
        list.sort((a, b) -> a.length() - b.length());
        System.out.println(list);
    }
}
          

Output

[API, Test, Java]
          

16. Converting Array to ArrayList

import java.util.*;

class Demo {
    public static void main(String[] args) {
        String[] arr = {"A", "B"};
        ArrayList<String> list = new ArrayList<>(Arrays.asList(arr));
        System.out.println(list);
    }
}
          

17. Converting ArrayList to Array

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B"));
        Object[] arr = list.toArray();
        System.out.println(arr.length);
    }
}
          

18. ensureCapacity() (Performance)

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.ensureCapacity(1000);
        System.out.println("Capacity ensured");
    }
}
          

Explanation

Reduces resizing overhead

19. Thread-Unsafe Nature of ArrayList

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Not Thread Safe");
        System.out.println(list);
    }
}
          

Explanation

Use Collections.synchronizedList() or CopyOnWriteArrayList

20. Interview Summary: ArrayList

import java.util.*;

class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Ordered");
        list.add("Dynamic");
        list.add("Duplicate");
        System.out.println(list);
    }
}
          

Key Points

  • Dynamic array
  • Index-based access (fast)
  • Allows duplicates
  • Maintains insertion order
  • Not thread-safe

Key Takeaway

Use ArrayList when fast access and iteration are required. Avoid it when frequent insertions/deletions in the middle or thread safety is a concern.