Java Notes
Learn ArrayList in Java from the ground up with clear explanations, practical examples, internal working, performance analysis, common pitfalls, and interview-focused insights designed for students and developers.
ArrayList is one of the most commonly used data structures in Java that stores elements in a dynamic array. Unlike traditional arrays, its size is not fixed, which means it can automatically grow or shrink as elements are added or removed.
It is part of the Java Collections Framework and implements the List interface. ArrayList maintains the insertion order of elements, allows duplicate values, and provides fast access to data using indexes.
Because of its simplicity, flexibility, and excellent performance for most day-to-day operations, ArrayList is often the first choice for developers when working with collections in Java.
Key Characteristics
ArrayList is popular because it combines the simplicity of arrays with the flexibility of dynamic memory management. Some of its important characteristics are:
- Resizable Structure – Grows and shrinks automatically as elements are added or removed.
- Maintains Insertion Order – Elements remain in the same order in which they were inserted.
- Supports Duplicate Values – The same element can appear multiple times.
- Allows Null Values – One or more null elements can be stored.
- Fast Random Access – Elements can be accessed quickly using indexes.
- Part of Collections Framework – Integrates seamlessly with other collection classes and utilities.
- Not Thread-Safe – Requires external synchronization when accessed by multiple threads simultaneously.
Why Use ArrayList?
In real-world applications, the number of elements is often unknown in advance. Using a traditional array requires deciding the size beforehand, which can lead to wasted memory or insufficient capacity.
ArrayList solves this problem by managing its storage automatically. Developers can focus on working with data instead of worrying about array sizes and memory allocation.
Because of its ease of use and excellent performance for most common operations, ArrayList is one of the most widely used collection classes in Java.
Class Hierarchy
ArrayList is built on top of the Java Collections Framework and inherits functionality from several abstract classes.
Implemented Interfaces
ArrayList implements several important interfaces that provide additional functionality.
| Interface | Purpose |
|---|---|
| List | Ordered collection of elements |
| RandomAccess | Supports fast index-based access |
| Cloneable | Allows object cloning |
| Serializable | Supports serialization |
| Iterable | Enables enhanced for-loop traversal |
Creating an ArrayList
Java provides multiple ways to create an ArrayList depending on the requirements of an application. You can create an empty list, specify an initial capacity, or initialize it using an existing collection.
Default Constructor
The default constructor creates an empty ArrayList. It is the most commonly used approach when the number of elements is unknown.
ArrayList<String> list = new ArrayList<>();Constructor with Initial Capacity
If you already know approximately how many elements will be stored, providing an initial capacity can reduce resizing operations and improve performance.
ArrayList<Integer> numbers = new ArrayList<>(50);Creating from Another Collection
An ArrayList can also be created from an existing collection. This is useful when converting other collection types into an ArrayList.
List<String> source =
Arrays.asList(
"Java",
"Python",
"JavaScript"
);
ArrayList<String> list =
new ArrayList<>(source);Basic Operations
Once an ArrayList has been created, elements can be added, accessed, modified, and removed using built-in methods.
Adding Elements
The add() method inserts a new element at the end of the list.
Adding elements is one of the most common operations performed on an ArrayList. Java provides the add() method to insert new elements into the list. By default, elements are added at the end, and the ArrayList automatically increases its capacity whenever additional space is needed.
This dynamic resizing behavior makes ArrayList much more flexible than traditional arrays, where the size must be defined in advance.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("JavaScript");
System.out.println(languages);Output
Time Complexity: O(1) (Amortized)
Accessing Elements
Elements can be retrieved using their index position.
One of the biggest advantages of ArrayList is its ability to provide fast access to elements using indexes. Since ArrayList is backed by a dynamic array internally, retrieving an element from a specific position is extremely efficient.
Indexes in ArrayList start from 0, which means the first element is stored at index 0, the second at index 1, and so on.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
System.out.println(
languages.get(0)
);Output
JavaTime Complexity: O(1)
Updating Elements
The set() method replaces an existing element at a specified index.
In many applications, data does not remain the same forever. There are situations where an existing value needs to be modified without removing and re-adding the element. ArrayList provides the set() method for this purpose.
The set() method replaces the element at a specified index with a new value while keeping the size of the list unchanged. Since ArrayList supports direct index-based access, updating an element is a very fast operation.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.set(
0,
"Spring Boot"
);
System.out.println(languages);Output
Time Complexity: O(1)
Removing Elements
Elements can be removed either by value or by index.
As data changes over time, some elements may no longer be needed. ArrayList provides several methods for removing elements, allowing developers to delete items either by their value or by their index position.
When an element is removed, ArrayList automatically shifts the remaining elements to fill the empty space. This ensures that the list remains continuous and maintains its insertion order.
The remove() method is one of the most frequently used operations when working with dynamic collections.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.remove("Java");
System.out.println(languages);Output
Time Complexity: O(n)
Internal Working of ArrayList
Understanding how ArrayList works internally is important for writing efficient Java programs. While ArrayList appears simple from the outside, a lot of work happens behind the scenes whenever elements are added, removed, or accessed.
Unlike LinkedList, which stores data in separate nodes, ArrayList stores its elements inside a dynamic array. This allows extremely fast index-based access while keeping memory usage relatively efficient.
Because many interview questions focus on the internal behavior of ArrayList, learning these concepts will help you understand not only how ArrayList works but also when it should be used in real-world applications.
Internal Data Structure
At its core, ArrayList uses an internal array to store elements. Whenever a new object is added to the list, it is actually stored inside this underlying array.
A simplified representation of the internal structure looks like this:
public class ArrayList<E> {
private Object[] elementData;
private int size;
}Here:
elementDatastores the actual elements.sizekeeps track of the number of elements currently present in the list.- The internal array may have a larger capacity than the current size of the list.
Capacity vs Size
One of the most common sources of confusion for beginners is the difference between capacity and size in an ArrayList. Although these terms are related, they represent two completely different concepts.
The size of an ArrayList refers to the number of elements currently stored in the list. The capacity, on the other hand, represents the total number of elements the internal array can hold before it needs to grow.
In simple terms, size tells us how many elements are present, while capacity tells us how much space has been allocated internally.
Example
ArrayList<Integer> numbers = new ArrayList<>(10);
System.out.println(numbers.size());Output
0Even though the ArrayList was created with a capacity of 10, its size is still 0 because no elements have been added yet.
Now let's add a few elements:
ArrayList<Integer> numbers = new ArrayList<>(10);
numbers.add(10);
numbers.add(20);
numbers.add(30);
System.out.println(numbers.size());Output
3The size becomes 3 because three elements are stored in the list, while the capacity remains 10.
Capacity vs Size Comparison
| Feature | Capacity | Size |
|---|---|---|
| Meaning | Total allocated storage | Number of stored elements |
| Managed By | Internal Array | ArrayList |
| Changes When | Resizing occurs | Elements are added or removed |
| Can Be Larger? | Yes | No |
| Accessible Directly? | No | Yes (size()) |
Key Points
- Capacity is always greater than or equal to size.
- Size increases when elements are added.
- Size decreases when elements are removed.
- Capacity only changes when the internal array needs more space.
- Understanding this difference helps explain why ArrayList can grow dynamically without requiring manual memory management.
Interview Tip: If an ArrayList has a capacity of 20 and currently stores 5 elements, its size is 5 while its capacity remains 20.
Dynamic Resizing Mechanism
One of the biggest advantages of ArrayList is its ability to grow automatically when more space is required. Unlike traditional arrays, where the size must be defined at the time of creation, ArrayList handles memory management behind the scenes.
Whenever the internal array becomes full and a new element is added, ArrayList creates a larger array, copies all existing elements into it, and then stores the new element. This process is known as dynamic resizing.
Because of this feature, developers can continue adding elements without worrying about the capacity of the collection.
How Resizing Works
When the internal array runs out of space:
- A new larger array is created.
- Existing elements are copied into the new array.
- The old array is discarded.
- The new element is inserted.
A simplified representation is shown below:
Simplified Internal Logic
if (size == elementData.length) {
int newCapacity =
oldCapacity +
(oldCapacity / 2);
elementData =
Arrays.copyOf(
elementData,
newCapacity
);
}In modern Java versions, ArrayList typically increases its capacity by 50% whenever resizing is required.
Example
Even though the default capacity is limited, the ArrayList automatically expands as more elements are added.
Why Resizing Matters
Dynamic resizing makes ArrayList extremely convenient, but it is not free. During a resize operation, all existing elements must be copied into a new array.
For very large collections, frequent resizing can affect performance.
Because of this, if you already know approximately how many elements will be stored, it is recommended to specify an initial capacity.
ArrayList<Integer> numbers =
new ArrayList<>(1000);This reduces the number of resize operations and improves performance.
Interview Tip: ArrayList provides O(1) amortized insertion at the end because resizing happens only occasionally. When resizing occurs, the operation becomes O(n) because all elements must be copied to a new array.
Time Complexity Analysis
When choosing a data structure, understanding its performance is just as important as understanding its features. Time complexity helps us estimate how efficiently an operation performs as the amount of data grows.
Since ArrayList is internally backed by a dynamic array, some operations are extremely fast, while others may require shifting elements or resizing the internal array.
The table below summarizes the average time complexity of common ArrayList operations.
Time Complexity Table
| Operation | Time Complexity | Explanation |
|---|---|---|
| add(element) | O(1) Amortized | Adds an element at the end |
| add(index, element) | O(n) | Elements must be shifted |
| get(index) | O(1) | Direct index access |
| set(index, element) | O(1) | Direct replacement |
| remove(index) | O(n) | Elements are shifted left |
| remove(object) | O(n) | Search + removal |
| contains(object) | O(n) | Linear search |
| indexOf(object) | O(n) | Linear search |
| size() | O(1) | Returns stored size value |
| isEmpty() | O(1) | Simple size check |
Why is get() O(1)?
One of the biggest strengths of ArrayList is fast random access.
Because elements are stored in a continuous array, Java can directly calculate the memory location of any element using its index.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
System.out.println(
languages.get(1)
);Output
PythonNo searching is required, which is why the operation takes constant time.
Why is add() O(1) Amortized?
When adding an element to the end of the list, ArrayList usually places it directly into the next available position.
languages.add("Spring");Most insertions are completed in constant time.
However, if the internal array becomes full, ArrayList must create a larger array and copy all existing elements. This resizing operation takes O(n) time.
Because resizing happens only occasionally, the average insertion cost is considered O(1) amortized.
Why is remove() O(n)?
Removing an element is more expensive because the remaining elements must be shifted.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Spring");
languages.remove(0);Before Removal:
After Removal:
To maintain a continuous sequence, every element after the removed position must move one step to the left.
This shifting process makes removal an O(n) operation.
Why is contains() O(n)?
The contains() method checks whether a specific element exists in the list.
languages.contains("Python");ArrayList searches one element at a time until a match is found.
Since it may need to inspect every element in the worst case, the complexity is O(n).
Performance Summary
ArrayList performs exceptionally well when:
- Frequent element access is required.
- Most insertions happen at the end.
- Random access is more important than frequent deletions.
ArrayList may not be the best choice when:
- Frequent insertions occur in the middle.
- Frequent deletions occur from the beginning.
- Constant shifting of elements becomes expensive.
Interview Tip: The most commonly asked ArrayList complexities are: -get()→ O(1) -set()→ O(1) -add()→ O(1) Amortized -remove()→ O(n) -contains()→ O(n)
Searching Elements
Searching is a common operation when working with collections. In many real-world applications, we often need to check whether a particular element exists, find its position, or locate multiple occurrences of the same value.
ArrayList provides several built-in methods that make searching simple and efficient. Although these methods are easy to use, it is important to understand how they work internally and their performance implications.
Using contains()
The contains() method checks whether a specific element exists in the ArrayList.
It returns:
trueif the element is found.falseif the element is not present.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Spring");
System.out.println(
languages.contains("Python")
);Output
trueHow It Works
Internally, ArrayList performs a linear search and compares each element until a match is found.
Because it may need to check every element, the time complexity is:
Time Complexity: O(n)
Using indexOf()
The indexOf() method returns the index of the first occurrence of a specified element.
If the element is not found, it returns -1.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Spring");
System.out.println(
languages.indexOf("Python")
);Output
1Element Not Found
System.out.println(
languages.indexOf("React")
);Output
-1Time Complexity: O(n)
Using lastIndexOf()
When duplicate values exist, lastIndexOf() returns the index of the last occurrence.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Java");
languages.add("Spring");
System.out.println(
languages.lastIndexOf("Java")
);Output
2This method is useful when you want to locate the most recent occurrence of an element.
Time Complexity: O(n)
Searching with Duplicate Elements
Consider the following example:
ArrayList<String> fruits =
new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");
fruits.add("Mango");| Method | Result |
|---|---|
| indexOf("Apple") | 0 |
| lastIndexOf("Apple") | 2 |
| contains("Apple") | true |
Key Points
contains()checks whether an element exists.indexOf()returns the first matching index.lastIndexOf()returns the last matching index.- All searching methods perform a linear search internally.
- Performance decreases as the size of the ArrayList grows.
Interview Tip: ArrayList does not use hashing or indexing for searching. Methods such ascontains(),indexOf(), andlastIndexOf()perform linear searches, which is why their time complexity is O(n).
Iterating Through an ArrayList
In most real-world applications, storing data is only part of the task. Developers often need to process, display, filter, or modify every element stored inside an ArrayList. This process is known as iteration.
Java provides multiple ways to iterate through an ArrayList. Each approach has its own advantages, and choosing the right one depends on the use case.
Enhanced For Loop (For-Each Loop)
The enhanced for loop is the simplest and most readable way to iterate through an ArrayList.
It automatically visits every element without requiring manual index management.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Spring");
for (String language : languages) {
System.out.println(language);
}Output
Java
Python
SpringAdvantages
- Easy to read and write.
- Best for simple traversal.
- No need to manage indexes.
Limitation
Elements cannot be safely removed while iterating.
Traditional For Loop
The traditional for loop provides direct access to indexes.
Output
Java
Python
SpringAdvantages
- Access to indexes.
- Useful when index values are required.
- Allows modification using index positions.
Using Iterator
An Iterator provides a safe way to traverse collections.
import java.util.Iterator;
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Spring");
Iterator<String> iterator =
languages.iterator();
while (iterator.hasNext()) {
System.out.println(
iterator.next()
);
}Output
Java
Python
SpringAdvantages
- Works with all collection types.
- Supports safe removal using
remove().
Removing Elements with Iterator
One of the biggest advantages of Iterator is that it allows safe removal during traversal.
Iterator<String> iterator =
languages.iterator();
while (iterator.hasNext()) {
String language =
iterator.next();
if (language.equals("Python")) {
iterator.remove();
}
}This prevents ConcurrentModificationException.
Using ListIterator
A ListIterator is a more powerful version of Iterator.
It supports:
- Forward traversal
- Backward traversal
- Element modification
- Element insertion
import java.util.ListIterator;
ListIterator<String> iterator =
languages.listIterator();
while (iterator.hasNext()) {
System.out.println(
iterator.next()
);
}Backward Traversal
while (iterator.hasPrevious()) {
System.out.println(
iterator.previous()
);
}Using forEach() Method
Java 8 introduced the forEach() method, making iteration more concise.
languages.forEach(
language ->
System.out.println(language)
);Output
Java
Python
SpringUsing Method Reference
languages.forEach(
System.out::println
);This is often considered the cleanest syntax for simple printing operations.
Using Stream API
The Stream API provides a modern and powerful way to process collections.
languages.stream()
.forEach(
System.out::println
);Streams become especially useful when filtering, sorting, and transforming data.
languages.stream()
.filter(
language ->
language.startsWith("J")
)
.forEach(
System.out::println
);Output
JavaComparison of Iteration Methods
| Method | Best Use Case |
|---|---|
| For-Each Loop | Simple traversal |
| Traditional For Loop | Index-based access |
| Iterator | Safe removal during iteration |
| ListIterator | Forward and backward traversal |
| forEach() | Cleaner Java 8 syntax |
| Stream API | Filtering and data processing |
Key Points
- For-Each loops are the easiest way to traverse an ArrayList.
- Traditional loops provide access to indexes.
- Iterator allows safe removal during iteration.
- ListIterator supports bidirectional traversal.
- forEach() offers concise Java 8 syntax.
- Stream API is ideal for modern data processing operations.
Interview Tip: Attempting to modify an ArrayList directly while using a For-Each loop may result in a ConcurrentModificationException. Use an Iterator when elements need to be removed during traversal.ArrayList vs LinkedList
Both ArrayList and LinkedList are popular implementations of the List interface in Java. At first glance, they may seem similar because both can store ordered collections of elements, allow duplicates, and provide many of the same methods.
However, their internal implementations are completely different, which has a significant impact on performance.
ArrayList stores elements inside a dynamic array, making it extremely fast for accessing data using indexes. LinkedList, on the other hand, stores elements in a series of connected nodes, where each node maintains references to the previous and next nodes.
Because of these structural differences, certain operations perform better in ArrayList, while others are more efficient in LinkedList.
Choosing the right data structure depends on the requirements of your application. Understanding their strengths and weaknesses will help you make better design decisions and answer interview questions with confidence.
ArrayList vs LinkedList Comparison
| Feature | ArrayList | LinkedList |
|---|---|---|
| Internal Structure | Dynamic Array | Doubly Linked List |
| Memory Usage | Lower | Higher |
| Access by Index | O(1) | O(n) |
| Search Operation | O(n) | O(n) |
| Insert at End | O(1) Amortized | O(1) |
| Insert at Beginning | O(n) | O(1) |
| Insert in Middle | O(n) | O(1)* |
| Remove from Beginning | O(n) | O(1) |
| Remove from Middle | O(n) | O(1)* |
| Cache Performance | Better | Poorer |
| Random Access | Excellent | Poor |
| Iteration Speed | Faster | Slower |
| Best For | Frequent Access | Frequent Insertions and Deletions |
| Implements | List, RandomAccess | List, Deque |
| Default Choice | Usually Preferred | Special Use Cases |
Note: Operations marked with O(1)* assume that the position of the node is already known. Finding the node itself may still require O(n) time.Both ArrayList and LinkedList implement the List interface, but they are designed for different use cases. ArrayList focuses on fast access and efficient memory usage, whereas LinkedList is optimized for frequent insertions and deletions. Understanding their differences helps developers choose the most suitable collection for a particular problem.
Which One Should You Choose?
For most applications, ArrayList is the preferred choice because it offers fast random access, better cache performance, and lower memory overhead.
LinkedList should only be considered when an application performs a large number of insertions and deletions, especially at the beginning or middle of the collection.
Interview Tip: If you are unsure which collection to choose, ArrayList is usually the safest and most practical default option.
Internal Structure
ArrayList
ArrayList uses a dynamic array internally.
Elements are stored in contiguous memory locations, allowing fast index-based access.
LinkedList
LinkedList uses a doubly linked list internally.
Each node stores:
- Data
- Reference to the previous node
- Reference to the next node
This structure makes insertion and deletion easier but slows down random access.
Performance Comparison
| Operation | ArrayList | LinkedList |
|---|---|---|
| Access by Index | O(1) | O(n) |
| Insert at End | O(1) Amortized | O(1) |
| Insert at Beginning | O(n) | O(1) |
| Insert in Middle | O(n) | O(1)* |
| Remove from Beginning | O(n) | O(1) |
| Remove from Middle | O(n) | O(1)* |
| Search | O(n) | O(n) |
| Memory Usage | Lower | Higher |
*Finding the position still requires traversal, which may take O(n).
When Should You Use ArrayList?
ArrayList is generally the preferred choice for most applications because it provides fast access to elements and better cache performance.
Use ArrayList when:
- Frequent element access is required.
- Most insertions happen at the end.
- Memory efficiency is important.
- Random access using indexes is common.
Examples:
- Student management systems
- Product catalogs
- Employee records
- Configuration data
When Should You Use LinkedList?
LinkedList becomes useful when elements are frequently inserted or removed from the beginning or middle of a collection.
Use LinkedList when:
- Frequent insertions and deletions occur.
- Queue-like behavior is needed.
- Random access is not important.
- Elements are constantly moving within the collection.
Examples:
- Task schedulers
- Browser history
- Undo/Redo systems
- Queue implementations
Real-World Perspective
In modern Java development, ArrayList is used far more frequently than LinkedList.
For most applications, the performance benefits of fast random access outweigh the insertion and deletion advantages provided by LinkedList.
This is why experienced Java developers usually start with ArrayList unless they have a specific reason to choose LinkedList.
Interview Tip: If an interviewer asks which collection you would choose by default, the safest answer is usually ArrayList, unless the application involves frequent insertions and deletions in the middle of the collection.
Common Mistakes and Best Practices
Learning how to use ArrayList is important, but understanding common mistakes is equally valuable. Many beginners encounter performance issues, unexpected exceptions, or inefficient code because they are unaware of how ArrayList works internally.
By following a few best practices and avoiding common pitfalls, you can write cleaner, more efficient, and more maintainable Java code.
Common Mistakes
1. Modifying an ArrayList During Iteration
One of the most common mistakes is modifying an ArrayList while iterating through it using a For-Each loop.
for (String language : languages) {
if (language.equals("Java")) {
languages.remove(language);
}
}This code may throw a ConcurrentModificationException.
Recommended Solution:
Use an Iterator when elements need to be removed during traversal.
Iterator<String> iterator =
languages.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("Java")) {
iterator.remove();
}
}2. Ignoring Initial Capacity
Many developers create large ArrayLists without specifying an initial capacity.
ArrayList<Integer> numbers =
new ArrayList<>();If thousands of elements are added, multiple resizing operations may occur.
Better Approach
ArrayList<Integer> numbers =
new ArrayList<>(10000);Providing an estimated capacity can improve performance by reducing resize operations.
3. Using LinkedList Without Need
Some beginners assume LinkedList is always faster because insertion and deletion are O(1).
In reality, ArrayList is often faster for most practical applications due to better cache performance and fast random access.
Always choose the collection based on actual requirements rather than theoretical complexity alone.
4. Accessing Invalid Indexes
Attempting to access an index that does not exist results in an exception.
ArrayList<String> languages =
new ArrayList<>();
languages.get(0);Output:
IndexOutOfBoundsExceptionAlways verify the size of the collection before accessing elements.
if (!languages.isEmpty()) {
System.out.println(
languages.get(0)
);
}5. Using ArrayList for Frequent Middle Insertions
ArrayList performs well for appending elements, but inserting elements repeatedly in the middle can be expensive.
list.add(0, "New Element");This operation requires shifting existing elements.
If frequent insertions and deletions are expected, consider LinkedList instead.
Best Practices
Use Generics
Always specify the type of data stored in the collection.
ArrayList<String> languages =
new ArrayList<>();Avoid:
ArrayList languages =
new ArrayList();Generics improve type safety and prevent runtime errors.
Program to the Interface
Prefer using the List interface instead of the concrete implementation.
List<String> languages =
new ArrayList<>();This makes it easier to switch implementations in the future.
Use Enhanced For Loops for Simple Traversal
for (String language : languages) {
System.out.println(language);
}This approach improves readability and reduces boilerplate code.
Use Streams for Data Processing
Java Streams provide a modern and expressive way to process collections.
languages.stream()
.filter(
language ->
language.startsWith("J")
)
.forEach(
System.out::println
);Streams are especially useful for filtering, mapping, and aggregation operations.
Choose the Right Collection
ArrayList is not always the perfect solution.
Before selecting a collection, consider:
- Access frequency
- Insertion frequency
- Deletion frequency
- Memory requirements
- Thread safety requirements
Choosing the correct data structure can significantly improve application performance.
Key Takeaways
- Avoid modifying an ArrayList directly during iteration.
- Specify an initial capacity when the expected size is known.
- Prefer ArrayList for fast access and general-purpose use.
- Use Generics to improve type safety.
- Program against interfaces rather than implementations.
- Select the most appropriate collection for the problem.
Interview Tip: Many ArrayList interview questions are based on common mistakes such as ConcurrentModificationException, resizing behavior, capacity vs size, and choosing ArrayList versus LinkedList.
Frequently Asked Interview Questions
ArrayList is one of the most frequently discussed topics in Java interviews. Interviewers often focus on its internal implementation, performance characteristics, and real-world usage scenarios.
The following questions cover the most important concepts that every Java developer should understand before an interview.
1. What is ArrayList in Java?
ArrayList is a dynamic array implementation of the List interface provided by the Java Collections Framework. It allows elements to be stored in insertion order, supports duplicate values, and automatically grows as new elements are added.
2. What is the Difference Between an Array and an ArrayList?
| Array | ArrayList |
|---|---|
| Fixed Size | Dynamic Size |
| Stores Primitive and Object Types | Stores Objects Only |
| Less Flexible | More Flexible |
| Faster for Fixed Data | Easier to Manage Dynamic Data |
3. How Does ArrayList Grow Dynamically?
When the internal array becomes full, ArrayList creates a larger array, copies existing elements into it, and then inserts the new element.
This process is known as dynamic resizing.
4. What is the Difference Between Capacity and Size?
Capacity refers to the amount of storage allocated internally.
Size refers to the number of elements currently stored.
ArrayList<Integer> numbers =
new ArrayList<>(10);Capacity = 10
Size = 0
5. Why is get() O(1)?
ArrayList stores elements inside a continuous array.
Because the memory location can be calculated directly using the index, accessing an element requires constant time.
list.get(5);Time Complexity: O(1)
6. Why is remove() O(n)?
When an element is removed, all subsequent elements must shift one position to the left.
list.remove(0);Because shifting may involve many elements, the operation takes O(n) time.
7. Does ArrayList Allow Duplicate Values?
Yes.
ArrayList allows multiple occurrences of the same value.
ArrayList<String> languages =
new ArrayList<>();
languages.add("Java");
languages.add("Java");Output
8. Does ArrayList Allow Null Values?
Yes.
ArrayList can store one or more null values.
ArrayList<String> languages =
new ArrayList<>();
languages.add(null);
languages.add("Java");Output
9. Is ArrayList Thread-Safe?
No.
ArrayList is not synchronized and therefore is not thread-safe by default.
If multiple threads modify an ArrayList simultaneously, unexpected behavior may occur.
10. How Can You Make an ArrayList Thread-Safe?
Using:
List<String> list =
Collections.synchronizedList(
new ArrayList<>()
);Or by using:
CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();11. What is the Difference Between ArrayList and LinkedList?
ArrayList uses a dynamic array internally, while LinkedList uses a doubly linked list.
ArrayList provides faster random access, whereas LinkedList performs better for frequent insertions and deletions.
12. What Happens When an Invalid Index is Accessed?
Java throws an exception.
list.get(100);Output
IndexOutOfBoundsException13. What is the Default Capacity of an ArrayList?
In modern Java implementations, the default constructor initially creates an empty array and allocates storage when the first element is added.
The first allocation typically provides a capacity of 10.
14. What is the Difference Between remove(int index) and remove(Object obj)?
list.remove(1);Removes the element at index 1.
list.remove("Java");Removes the object "Java".
This is a very common interview question.
15. Why is ArrayList Preferred in Most Applications?
ArrayList offers:
- Fast random access
- Better cache performance
- Lower memory overhead
- Easy implementation
- Excellent overall performance
For these reasons, ArrayList is often the default choice for general-purpose collections.
Quick Revision
| Question | Short Answer |
|---|---|
| Dynamic or Fixed? | Dynamic |
| Allows Duplicates? | Yes |
| Allows Null? | Yes |
| Thread-Safe? | No |
| get() Complexity | O(1) |
| remove() Complexity | O(n) |
| Search Complexity | O(n) |
| Internal Structure | Dynamic Array |
| Interface Implemented | List |
| Best Use Case | Fast Random Access |
Interview Tip: If you understand dynamic resizing, capacity vs size, time complexity, ArrayList vs LinkedList, and thread safety, you can answer most ArrayList interview questions confidently.
Real-World Use Cases
ArrayList is widely used in real-world Java applications because of its flexibility and efficient random access performance.
Some common use cases include:
- Managing student records in educational systems
- Storing product catalogs in e-commerce applications
- Maintaining user lists in social media platforms
- Processing data retrieved from databases
- Managing task lists in productivity applications
- Storing API response data before processing
Because ArrayList provides dynamic sizing and fast element access, it is often the default choice for handling collections of data in business applications.
Complete Runnable Example
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
public class ArrayListDemo {
public static void main(String[] args) {
// Creating ArrayList
ArrayList<String> fruits = new ArrayList<>();
// Adding elements
fruits.add("Mango");
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple"); // Duplicates allowed
fruits.add(null); // Null allowed
System.out.println("Original: " + fruits);
System.out.println("Size: " + fruits.size());
// Accessing elements
System.out.println("Element at index 1: " + fruits.get(1));
System.out.println("Contains Banana: " + fruits.contains("Banana"));
System.out.println("Index of Apple: " + fruits.indexOf("Apple"));
// Modifying elements
fruits.set(4, "Grapes"); // Replace null with Grapes
System.out.println("After set: " + fruits);
// Removing elements
fruits.remove("Apple"); // Removes first occurrence
System.out.println("After remove Apple: " + fruits);
// Sorting
fruits.remove(null);
Collections.sort(fruits);
System.out.println("Sorted: " + fruits);
// Iterating with Iterator
System.out.print("Iterator: ");
Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
// SubList
System.out.println("SubList(0,2): " + fruits.subList(0, 2));
// Clear
fruits.clear();
System.out.println("After clear, isEmpty: " + fruits.isEmpty());
}
}Original: [Mango, Apple, Banana, Apple, null] Size: 5 Element at index 1: Apple Contains Banana: true Index of Apple: 1 After set: [Mango, Apple, Banana, Apple, Grapes] After remove Apple: [Mango, Banana, Apple, Grapes] Sorted: [Apple, Banana, Grapes, Mango] Iterator: Apple Banana Grapes Mango SubList(0,2): [Apple, Banana] After clear, isEmpty: true
Interview Questions
Q1: What is the default initial capacity of ArrayList?
A: The default initial capacity is 10. When elements are added beyond this capacity, the array is resized to approximately 1.5 times its current size (specifically oldCapacity + (oldCapacity >> 1)). Starting from Java 8, the internal array starts as an empty array and is initialized to capacity 10 on the first add() call.
Q2: What is the difference between ArrayList and LinkedList?
A: ArrayList uses a dynamic array internally — it provides O(1) random access but O(n) insertion/deletion in the middle. LinkedList uses a doubly-linked list — it provides O(1) insertion/deletion at ends but O(n) random access. ArrayList has better cache locality and less memory overhead per element.
Q3: How does ArrayList handle resizing internally?
A: When the internal array is full, ArrayList creates a new array with capacity oldCapacity + (oldCapacity >> 1) (roughly 1.5x), then uses Arrays.copyOf() (which internally calls System.arraycopy()) to copy all elements. This is why add() is O(1) amortized — most adds are O(1) but occasional resizes are O(n).
Q4: Is ArrayList thread-safe? How do you make it thread-safe?
A: No, ArrayList is NOT thread-safe. Options: (1) Use Collections.synchronizedList(new ArrayList<>()) for a synchronized wrapper, (2) Use CopyOnWriteArrayList from java.util.concurrent for read-heavy scenarios, (3) Use explicit synchronization with locks.
Q5: What is the difference between size() and capacity in ArrayList?
A: size() returns the number of elements actually stored. Capacity is the length of the internal array (total slots available). Size ≤ capacity always. There's no direct method to get capacity — use ensureCapacity() to set minimum capacity or trimToSize() to reduce capacity to current size.
Q6: Can ArrayList store primitive types?
A: No, ArrayList only stores objects (reference types). For primitives, Java uses autoboxing: ArrayList<Integer> instead of ArrayList<int>. Each primitive is automatically wrapped in its wrapper class. For performance-critical code with primitives, use arrays or specialized libraries like Eclipse Collections IntArrayList.
Q7: What happens when you call remove(int) vs remove(Object) with Integer elements?
A: For ArrayList<Integer>: remove(2) removes the element at index 2 (int parameter triggers index-based removal). To remove the Integer object with value 2, use remove(Integer.valueOf(2)) or remove((Integer) 2) to force object-based removal.
Q8: What is the difference between ArrayList and Vector?
A: Both use dynamic arrays, but: (1) Vector is synchronized (thread-safe but slow), ArrayList is not. (2) Vector doubles its capacity when full; ArrayList grows by 50%. (3) Vector is a legacy class (Java 1.0); ArrayList is part of Collections Framework (Java 1.2). (4) Vector's elements() returns Enumeration; ArrayList uses Iterator.
Q9: How do you remove elements from ArrayList while iterating?
A: Use Iterator.remove() (safe during iteration), removeIf(predicate) (Java 8+), or iterate backwards with index: for (int i = list.size()-1; i >= 0; i--). NEVER use list.remove() inside a for-each loop — it throws ConcurrentModificationException.
Q10: What is the time complexity of ArrayList operations?
A: get(index): O(1), add(element) at end: O(1) amortized, add(index, element): O(n), remove(index): O(n), contains(object): O(n), set(index, element): O(1), size(): O(1), isEmpty(): O(1). The O(n) operations involve shifting elements via System.arraycopy.
Q11: What is the difference between Arrays.asList() and new ArrayList<>()?
A: Arrays.asList() returns a fixed-size list backed by the original array — you can modify elements but cannot add/remove (throws UnsupportedOperationException). new ArrayList<>(Arrays.asList(...)) creates an independent, fully modifiable ArrayList. In Java 9+, List.of() creates an immutable list.
Q12: How does ArrayList's ensureCapacity() method help performance?
A: If you know you'll add many elements, calling ensureCapacity(n) pre-allocates the internal array to size n, avoiding multiple costly resize operations. For example, adding 10,000 elements without ensureCapacity triggers ~17 resizes; with ensureCapacity(10000), zero resizes occur.
Summary
ArrayList is one of the most important and frequently used collection classes in Java. It provides a dynamic and flexible way to store data while maintaining insertion order and supporting duplicate values.
Throughout this guide, we explored its internal implementation, dynamic resizing mechanism, performance characteristics, searching techniques, iteration methods, and best practices.
Understanding how ArrayList works internally helps developers write more efficient code and make better decisions when choosing data structures.
Key Takeaways
- ArrayList is a dynamic array implementation of the List interface.
- It maintains insertion order and allows duplicate values.
- Elements can be accessed using indexes in O(1) time.
- The collection grows automatically through dynamic resizing.
- Adding elements at the end is O(1) amortized.
- Removing elements may require shifting and takes O(n) time.
- ArrayList is not thread-safe by default.
- It is generally preferred over LinkedList for most applications.
- Understanding capacity, size, and internal resizing is important for interviews.
- ArrayList is one of the most commonly used data structures in Java development.
Conclusion
ArrayList is a fundamental part of the Java Collections Framework and an essential tool for every Java developer. Its combination of simplicity, flexibility, and performance makes it suitable for a wide range of applications.
Whether you are building enterprise software, preparing for technical interviews, or learning Java for the first time, a solid understanding of ArrayList will strengthen your foundation in data structures and collections.
Mastering ArrayList is not just about learning methods—it is about understanding how data is stored, accessed, and managed efficiently in real-world applications.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for ArrayList in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, collections, framework, list
Related Java Master Course Topics