Array vs ArrayList in Java — Primitives, Generics, Resize, and When to Use Each
Fast recall
Quick revision
Core answer
int[], String[]) are fixed-size, support primitives directly (int[] stores ints with no boxing), have covariant typing (String[] is an Object[]), and provide O(1) random access and O(1) length. ArrayList is a resizable-array wrapper that only holds objects (ArrayList boxes ints into Integer), offers a rich API (add, remove, contains, subList, iterator, stream), and grows amortized O(1) on append. Use arrays when: (1) you need primitives without boxing overhead (numerical algorithms, large data), (2) size is fixed and known, (3) you need multi-dimensional data (int[][] for matrices, 2D grids), or (4) you're interfacing with native code or legacy APIs. Use ArrayList when: (1) size is dynamic or unknown, (2) you need the Collection API (stream, iterator, sort, filter), (3) you want to hold objects (Generic types work cleanly), or (4) you're doing regular business logic (the 95% case). Key differences: arrays have `.length` (field, no parens); ArrayList has `.size()` (method). Arrays allow covariant assignment that can fail at runtime (Object[] arr = new String[1]; arr[0] = 1; throws ArrayStoreException); generics are invariant and catch this at compile time. Arrays can hold primitives or reference types; ArrayList can hold only reference types — primitives must be boxed (use Eclipse Collections / FastUtil for primitive-specialized lists if boxing overhead matters). For converting between them: Arrays.asList(arr) (fixed-size view, careful!), List.of(arr) (immutable), new ArrayList<>(Arrays.asList(arr)) (mutable copy), and list.toArray(new T[0]) back to array. Performance: arrays are marginally faster (no wrapper indirection) but JIT usually eliminates the gap for ArrayList; memory difference is significant for primitives (int vs Integer = 4 bytes vs 16+ bytes per entry).Whether the candidate knows the fixed vs dynamic size, primitive vs object distinction, the `.length` vs `.size()` gotcha, and the covariance hazard of arrays vs invariance of generics
Saying ArrayList stores primitives (it doesn't; it boxes). Confusing `.length` with `.length()` (the former is for arrays, the latter for String). Not knowing arrays are covariant (a runtime-safety hazard)
Discuss array covariance hazard with ArrayStoreException, mention primitive-specialized libraries (Eclipse Collections, FastUtil, Trove), note JIT can close the performance gap, and explain memory overhead of boxing
Interview-ready guide
Interview answer
Arrays are fixed-size containers that support primitives natively. ArrayList is a resizable wrapper around an array that only holds objects. Pick based on your needs.
Use arrays when
- You need primitives without boxing (
int[],double[]) - Size is fixed and known
- Multi-dimensional data (
int[][]for matrices) - Performance-critical loops (JIT optimizes arrays extremely well)
- Interop with native code or legacy APIs
Use ArrayList when
- Size is dynamic or unknown
- You need the Collection API (stream, iterator, sort, filter)
- You want to hold objects (generics work cleanly)
- Regular business logic — the 95% case
Key differences
Size accessor
- Array:
.length(field, no parens) - ArrayList:
.size()(method) - Common trap for beginners
Primitives
- Array:
int[],double[]— no boxing - ArrayList:
ArrayList— boxes every int - Memory:
int[]≈ 4 bytes/entry;ArrayList≈ 24 bytes/entry
Covariance
- Arrays are covariant:
String[]isObject[] - Convenient but unsafe:
ArrayStoreExceptionat runtime - Generics are invariant:
Listis NOTList - Compile-time safety wins
Generic arrays disallowed
new List— compile error[10] - Use nested
Listinstead- >
Converting
Array → List
Arrays.asList(arr)— fixed-size view (can't add/remove)List.of(arr)(Java 9+) — immutable copynew ArrayList<>(Arrays.asList(arr))— mutable copy
Primitive array → List
Arrays.asList(intArr)givesListwith ONE element (wrong!)- Use
Arrays.stream(intArr).boxed().toList()
List → Array
list.toArray(new T[0])— preferred (Java 8+ optimized)- Faster than
new T[list.size()]
List
list.stream().mapToInt(Integer::intValue).toArray()
Primitive-specialized collections — when boxing matters
- Eclipse Collections —
IntArrayList,IntIntMap - FastUtil — extensive primitive collections
- Trove — older but widely used
- Use when profiling shows
java.lang.Integerallocations dominating
Java built-in primitive alternatives
int[],long[],double[]BitSet— compact booleansIntStream,LongStream,DoubleStream— primitive stream pipelinesByteBuffer— off-heap primitive buffers
Performance
- Arrays marginally faster (no wrapper indirection)
- JIT often closes the CPU gap
- Memory difference matters more: boxing overhead for primitive collections
ArrayListhas better API (stream, sort, filter)
Common traps
1. `.length` vs `.length()` vs `.size()`
- Array:
.length(field) - String:
.length()(method) - Collection:
.size()(method)
2. Arrays.asList on primitive array
Arrays.asList(intArr)returnsList, notList- Use
Arrays.stream(arr).boxed().toList()instead
3. Array covariance bug
Object[] arr = new String[3]; arr[0] = 42;compiles but throws ArrayStoreException- Generics catch at compile time
4. Fixed-size view from Arrays.asList
- Can't add/remove; for mutable, wrap in
new ArrayList<>(...)
5. Generic array creation forbidden
new List— compile error[10] - Use nested lists
Project Valhalla (future)
- Will allow
Listwithout boxing - Primitive classes, generic specialization
- Not available yet in production
Recommendation: For business logic (the 95% case), default to ArrayList — dynamic size, rich Collection API, generics for type safety, plays well with streams and frameworks. Reach for arrays when you have a clear reason: primitives (numerical algorithms, large data where boxing costs hurt), fixed size known at creation, multi-dimensional data (int[][] for matrices), or performance-critical hot loops where JIT-optimized array ops win. Remember the covariance hazard: String[] masquerades as Object[] and will throw ArrayStoreException at runtime — generics catch this at compile time. For converting, use list.toArray(new T[0]) (JDK-optimized since Java 8), Arrays.stream(intArr).boxed().toList() for primitive-to-boxed, and new ArrayList<>(Arrays.asList(arr)) for mutable conversions (plain Arrays.asList returns a fixed-size view). For huge primitive data where boxing overhead dominates, use Eclipse Collections, FastUtil, or Trove. Never use Arrays.asList(intArr) on a primitive array — it returns a singleton list containing the array itself, not the ints. The mental model: arrays = primitives-friendly, fixed, covariant (risky); ArrayList = objects only, dynamic, invariant (safe), API-rich. In most Java code, ArrayList is the right default.
Array vs ArrayList in Java — Primitives, Generics, Resize, and When to Use Each
Direct Answer
Arrays (int[], String[]) are fixed-size, support primitives directly (int[] stores ints with no boxing), have covariant typing (String[] is an Object[]), and provide O(1) random access and O(1) length. ArrayList is a resizable-array wrapper that only holds objects (ArrayList<Integer> boxes ints into Integer), offers a rich API (add, remove, contains, subList, iterator, stream), and grows amortized O(1) on append. Use arrays when: (1) you need primitives without boxing overhead (numerical algorithms, large data), (2) size is fixed and known, (3) you need multi-dimensional data (int[][] for matrices, 2D grids), or (4) you're interfacing with native code or legacy APIs. Use ArrayList when: (1) size is dynamic or unknown, (2) you need the Collection API (stream, iterator, sort, filter), (3) you want to hold objects (Generic types work cleanly), or (4) you're doing regular business logic (the 95% case). Key differences: arrays have .length (field, no parens); ArrayList has .size() (method). Arrays allow covariant assignment that can fail at runtime (Object[] arr = new String[1]; arr[0] = 1; throws ArrayStoreException); generics are invariant and catch this at compile time. Arrays can hold primitives or reference types; ArrayList can hold only reference types — primitives must be boxed (use Eclipse Collections / FastUtil for primitive-specialized lists if boxing overhead matters). For converting between them: Arrays.asList(arr) (fixed-size view, careful!), List.of(arr) (immutable), new ArrayList<>(Arrays.asList(arr)) (mutable copy), and list.toArray(new T[0]) back to array. Performance: arrays are marginally faster (no wrapper indirection) but JIT usually eliminates the gap for ArrayList; memory difference is significant for primitives (int vs Integer = 4 bytes vs 16+ bytes per entry).
Common Mistakes & Pitfalls
Saying ArrayList stores primitives (it doesn't; it boxes). Confusing .length with .length() (the former is for arrays, the latter for String). Not knowing arrays are covariant (a runtime-safety hazard)
Check your understanding
Answer these without looking back at the article.
- Why are arrays covariant but generics invariant?
- What happens when you call `Arrays.asList(intArray)` on an int[]?
- Why is `list.toArray(new T[0])` faster than `list.toArray(new T[list.size()])`?
- Why can't you create a generic array with `new List<String>[10]`?
- When would you use Eclipse Collections or FastUtil over ArrayList?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.