Skip to content

Best Practices & Pitfalls

  • ArrayList: Choose when:

    • Random access is frequent
    • List size doesn’t change much
    • Iteration is the primary operation
  • LinkedList: Choose when:

    • Frequent insertions/deletions at both ends
    • Size changes frequently
    • Random access is rare
  • HashSet: Choose when:

    • Fastest possible lookup is needed
    • Order doesn’t matter
    • Elements have good hashCode() implementation
  • LinkedHashSet: Choose when:

    • Insertion order must be preserved
    • Fast lookup is needed
  • TreeSet: Choose when:

    • Elements must be sorted
    • Range queries are needed
  • EnumSet: Choose when:

    • Elements are all of the same enum type
  • HashMap: Choose when:

    • Fastest possible key lookup is needed
    • Order doesn’t matter
    • Keys have good hashCode() implementation
  • LinkedHashMap: Choose when:

    • Insertion or access order must be preserved
    • Fast key lookup is needed
  • TreeMap: Choose when:

    • Keys must be sorted
    • Range queries are needed
  • EnumMap: Choose when:

    • Keys are all of the same enum type
  1. Specify initial capacity when you know the approximate size:

    // Better performance if you know you'll add ~1000 elements
    List<String> list = new ArrayList<>(1000);
    Map<String, Integer> map = new HashMap<>(1000);
  2. Use specialized collections for primitive types to avoid boxing/unboxing:

    // Instead of List<Integer>
    IntList intList = new IntArrayList(); // From Eclipse Collections or similar library
    // Or use streams for primitives
    int[] array = IntStream.range(0, 1000).toArray();
  3. Choose the right iteration style for your collection:

    // For ArrayList, indexed for-loop is fastest
    for (int i = 0; i < arrayList.size(); i++) {
    String item = arrayList.get(i);
    }
    // For LinkedList, iterator or enhanced for-loop is better
    for (String item : linkedList) {
    // Process item
    }
  4. Avoid unnecessary autoboxing/unboxing:

    // Inefficient - causes boxing/unboxing
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < 1000000; i++) {
    map.put(i, i * 2);
    }
    // Better alternatives:
    // 1. Use primitive specialized libraries like Trove, Eclipse Collections
    // 2. Minimize operations in tight loops
  5. Use bulk operations when available:

    // Instead of adding elements one by one
    list.addAll(Arrays.asList("a", "b", "c"));
    // Instead of removing elements one by one
    list.removeAll(elementsToRemove);
  6. Consider memory usage:

    // For very large sets of enum values, EnumSet is much more memory efficient
    EnumSet<MyEnum> enumSet = EnumSet.noneOf(MyEnum.class);
    // For small, fixed collections, consider array-based alternatives
    List<String> smallList = Arrays.asList("a", "b", "c");
  1. Choosing the wrong collection type for your use case:

    // Bad: Using LinkedList for random access
    List<String> list = new LinkedList<>();
    for (int i = 0; i < list.size(); i++) {
    String item = list.get(i); // O(n) operation for LinkedList
    }
    // Good: Use ArrayList instead
    List<String> list = new ArrayList<>();
  2. Not overriding equals() and hashCode() for custom classes used in HashSet/HashMap:

    // Without proper equals() and hashCode(), this won't work as expected
    Set<Customer> customers = new HashSet<>();
    customers.add(new Customer("John", 123));
    boolean contains = customers.contains(new Customer("John", 123)); // Will return false
  3. Modifying a collection while iterating without using the proper approach:

    // Will throw ConcurrentModificationException
    for (String item : list) {
    if (item.startsWith("A")) {
    list.remove(item);
    }
    }
    // Correct approach
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.startsWith("A")) {
    iterator.remove();
    }
    }
  4. Not considering thread safety when needed:

    // Unsafe in multi-threaded environment
    Map<String, Integer> map = new HashMap<>();
    // Thread-safe alternatives
    Map<String, Integer> safeMap = Collections.synchronizedMap(new HashMap<>());
    Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
  5. Inefficient contains() operations on lists:

    // O(n) operation on ArrayList
    List<String> list = new ArrayList<>();
    // ... add many items
    if (list.contains("needle")) { // Linear search
    // ...
    }
    // More efficient for frequent lookups
    Set<String> set = new HashSet<>(list);
    if (set.contains("needle")) { // O(1) operation
    // ...
    }
  1. Implementing equals() and hashCode() correctly:

    public class Person {
    private final String name;
    private final int age;
    // Constructor, getters...
    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return age == person.age && Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
    return Objects.hash(name, age);
    }
    }
  2. Implementing Comparable for natural ordering:

    public class Person implements Comparable<Person> {
    private final String name;
    private final int age;
    // Constructor, getters, equals, hashCode...
    @Override
    public int compareTo(Person other) {
    int nameComparison = this.name.compareTo(other.name);
    if (nameComparison != 0) {
    return nameComparison;
    }
    return Integer.compare(this.age, other.age);
    }
    }
  3. Creating immutable collections when content shouldn’t change:

    // Java 9+
    List<String> immutableList = List.of("a", "b", "c");
    // Java 8 and earlier
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    List<String> immutableList = Collections.unmodifiableList(list);
  4. Using specialized collections for specific use cases:

    // LRU cache with LinkedHashMap
    Map<String, String> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
    return size() > MAX_CACHE_SIZE;
    }
    };
  5. Implementing custom collections by extending AbstractList, AbstractSet, etc.:

    public class CircularArrayList<E> extends AbstractList<E> {
    private final E[] elements;
    private int head = 0;
    private int size = 0;
    @SuppressWarnings("unchecked")
    public CircularArrayList(int capacity) {
    elements = (E[]) new Object[capacity];
    }
    @Override
    public E get(int index) {
    if (index < 0 || index >= size) {
    throw new IndexOutOfBoundsException();
    }
    return elements[(head + index) % elements.length];
    }
    @Override
    public int size() {
    return size;
    }
    // Additional methods for circular behavior...
    }