Once the store has objects, it needs groups of objects. This chapter teaches lists, sets, maps, queues, iteration, equality rules, and collection selection through an in-memory catalog and cart workflow. The project context keeps the code concrete, but the real goal is to build strong Java collection instincts that also help in interviews.

Why Chapter 4 moves from one object to many objects

Chapters 2 and 3 focused on designing a single Product, CartItem, or Customer object well. Real programs rarely stop there. A store needs many products, many cart lines, many customers, and fast ways to search, group, and update them. Collections are the Java tools that make those groups manageable.

This chapter uses an in-memory catalog so the learner can focus on collection thinking before databases arrive. That means the code is still practical, but the chapter is really about Java collection contracts, iteration patterns, equality rules, and how to choose the right structure for the job.

  • Collections model groups of values and objects.
  • The main question is not "which class exists" but "which contract fits the job".
  • Interview questions often test whether you understand behavior, not just names.

List<Product> catalog = new ArrayList<>();
Map<String, Product> productsBySku = new HashMap<>();
Set<String> featuredTags = new HashSet<>();

List and ArrayList: ordered data with index-based access

A List keeps insertion order and allows duplicate elements. That makes it a natural fit when order matters or when the same kind of item may appear multiple times. In Java, ArrayList is the most common implementation because it offers fast indexed reads and compact storage for general-purpose use.

ArrayList is backed by a resizable array. Reading by index is fast, but inserting or removing in the middle requires shifting elements. This is why developers should think in terms of usage patterns rather than assuming one structure fits every situation.

  • Use List when order matters and duplicates are allowed.
  • ArrayList is usually the default list choice in Java.
  • Middle inserts and removals are more expensive than simple appends.

List<String> productNames = new ArrayList<>();
productNames.add("Keyboard");
productNames.add("Mouse");
String first = productNames.get(0);

Set and HashSet: uniqueness as a contract

A Set represents uniqueness. If the same element is added twice, the second add does not create another copy. This matters when duplicates would be a correctness problem, such as tracking distinct categories, user roles, or already-seen IDs.

HashSet is the usual implementation when ordering does not matter and fast membership checks do. But HashSet only behaves correctly when the stored objects implement equals and hashCode consistently. That is why this chapter connects collections to the object design work from Chapter 3.

  • Use Set when uniqueness is the rule you care about.
  • HashSet is good for membership checks and duplicate prevention.
  • Broken equals/hashCode logic breaks hash-based collections.

Set<String> activeCoupons = new HashSet<>();
activeCoupons.add("WELCOME10");
activeCoupons.add("WELCOME10");
System.out.println(activeCoupons.size()); // 1

Map and HashMap: key-based lookup

A Map stores key-value pairs. This is the right tool when one piece of information should lead directly to another, such as SKU to Product or email to Customer. Instead of scanning a list every time, a map lets you model lookup explicitly.

HashMap is the most common implementation for general lookup work. It does not preserve sorted order, but it gives fast access for insert, read, and update in the common case. Interviewers often ask when to use a map because it reveals whether you notice when direct lookup matters more than plain iteration.

  • Use Map when lookup by key is the main need.
  • HashMap is usually the default unsorted map implementation.
  • A map communicates intent more clearly than a repeatedly scanned list.

Map<String, Product> productsBySku = new HashMap<>();
productsBySku.put("SKU-101", keyboard);
Product match = productsBySku.get("SKU-101");

Queues, deques, and traversal habits

Queues model first-in, first-out processing. Deques can act as queues or stacks depending on which end you use. In everyday Java, ArrayDeque is a strong general-purpose choice for queue and stack style operations because it avoids the legacy issues of Stack and often fits better than LinkedList.

This topic matters in interviews because breadth-first processing, buffering work, and command history often depend on queue or deque behavior. Even when the e-commerce project uses only small examples, the underlying Java skill is about understanding ordering contracts and operation style.

  • Queue means first-in, first-out behavior.
  • Deque supports operations at both ends.
  • ArrayDeque is often preferred over Stack for stack-like behavior in modern Java.

Deque<String> reviewQueue = new ArrayDeque<>();
reviewQueue.addLast("order-1");
reviewQueue.addLast("order-2");
String next = reviewQueue.removeFirst();

Iteration, equality, and choosing the right collection

Collections are not just about storing data. They are about how you traverse, search, compare, and update data safely. The enhanced for-loop is often the clearest way to read a collection, but iterators matter when removal during traversal is involved. Equality also matters because collections depend on it in different ways: lists care about order, sets care about uniqueness, and maps care about key identity through equals and hashCode.

Strong Java answers compare collections by contract. If you need order, say so. If you need uniqueness, say so. If you need lookup, say so. If you need to explain performance, focus on the dominant operations instead of reciting every complexity class from memory.

  • Choose collections by contract: order, uniqueness, lookup, or processing style.
  • Use iterators when you must remove safely during traversal.
  • Performance reasoning should match the main operations the code actually performs.

for (Product product : catalog) {
    if (product.isLowStock()) {
        System.out.println(product.getName());
    }
}

Chapter takeaway

Good Java developers do not memorize collection names in isolation. They know which contract they need, what performance tradeoff they are accepting, and how equality rules change collection behavior.