Java List vs Set vs Map
Java List vs Set vs Map
Three interfaces cover almost every collection decision in Java. List when order and position matter, Set when uniqueness matters, Map when lookup by a meaningful key matters. Getting this choice wrong produces correctness bugs, O(n) operations where O(1) is possible, or duplicate data appearing where it should not. The decision is architectural — it shapes how you model your data, not just which class name you type.
The Three Core Collection Interfaces
List, Set, and Map are the three fundamental contracts in the Java Collections Framework. Every concrete collection class you use — ArrayList, HashSet, HashMap, TreeMap — implements one of these.
java.util.Collection<E> ← root of element-holding collections
├── java.util.List<E> ← ordered, indexed, duplicates allowed
│ ├── ArrayList ← resizable array
│ ├── LinkedList ← doubly-linked nodes
│ └── CopyOnWriteArrayList ← thread-safe snapshot
│
└── java.util.Set<E> ← unique elements, no duplicates
├── HashSet ← O(1), no order
├── LinkedHashSet ← O(1), insertion order
└── TreeSet ← O(log n), sorted order
java.util.Map<K, V> ← key-value pairs (NOT a Collection)
├── HashMap ← O(1), no order
├── LinkedHashMap ← O(1), insertion order
├── TreeMap ← O(log n), sorted by key
└── ConcurrentHashMap ← thread-safe, bucket-level locking
Basic Overview — The Three Contracts at a Glance
LIST SET MAP
--------------------------- --------------------------- ---------------------------
Stores: elements Stores: elements Stores: key-value pairs
Order: YES — insertion order Order: depends on impl Order: depends on impl
Duplicates: ALLOWED Duplicates: REJECTED Keys: UNIQUE, values: any
Index access: YES (get(i)) Index access: NO Key access: YES (get(key))
Null: allowed Null: 1 null (HashSet) Null key: 1 (HashMap)
Primary use: ordered sequence Primary use: unique lookup Primary use: key→value lookup
Core question: Core question: Core question:
"What is at position i?" "Have I seen this before?" "What is the value for key X?"
SIZE: 3 elements SIZE: 3 unique elements SIZE: 3 key-value pairs
list = ["A","B","A"] set = {"A", "B"} map = {A→1, B→2, C→3}
list.size() = 3 set.size() = 2 map.size() = 3
list.get(1) = "B" set.contains("A") = true map.get("A") = 1
list.get(2) = "A" (second "A" was rejected) (each key maps to one value)
What Is List?
List<E> is an ordered, indexed sequence that allows duplicate elements. "Ordered" means elements stay in the position they were placed — iterating a List always produces elements in the same sequence. "Indexed" means you can retrieve any element directly by its zero-based position with get(int index). "Allows duplicates" means the same object can appear multiple times at different positions.
LIST CONTRACT:
- Maintains insertion order
- Elements accessible by index: get(0), get(1), ..., get(n-1)
- Duplicates: allowed — "Java" can appear at index 0 AND index 5
- Null: allowed
- Interface methods beyond Collection: get(i), set(i,e), add(i,e), remove(i),
indexOf(e), lastIndexOf(e), subList(from, to), listIterator()
COMMON IMPLEMENTATIONS:
ArrayList — O(1) get, O(n) add/remove at middle, cache-friendly
LinkedList — O(1) head/tail, O(n) get by index, implements Deque too
Vector — legacy, synchronised; use CopyOnWriteArrayList instead
What Is Set?
Set<E> enforces one rule above all: no duplicate elements. Adding an element that is already present returns false and leaves the set unchanged. The definition of "same element" is determined by equals() and hashCode() — any class used as a Set element must implement both correctly.
SET CONTRACT:
- No duplicate elements — add() returns false for duplicates
- No index access — cannot call get(i)
- No guaranteed ordering (for HashSet), insertion order (LinkedHashSet),
or sorted order (TreeSet)
- Null: HashSet allows one null; TreeSet throws NPE on null
- Interface methods: add(e), remove(e), contains(e), size(), iterator()
COMMON IMPLEMENTATIONS:
HashSet — O(1) add/remove/contains average, no order
LinkedHashSet — O(1) operations, preserves insertion order
TreeSet — O(log n) operations, sorted ascending order,
adds: first(), last(), floor(), ceiling(), headSet(), tailSet()
What Is Map?
Map<K, V> stores key-value pairs where each key is unique. It is NOT a subtype of Collection — it has its own root interface. Maps answer the question "given this key, what is the value?" in O(1) average (HashMap) or O(log n) worst-case (TreeMap). Every HashMap.containsKey() is O(1); every ArrayList.contains() is O(n). That difference is why switching from a list-of-pairs to a Map is one of the most impactful performance improvements in production Java code.
MAP CONTRACT:
- Unique keys — each key maps to at most one value
- Values: not constrained, duplicates allowed, null allowed
- Null keys: HashMap allows one; TreeMap/Hashtable do not
- Not a Collection — no add(), no iterator() directly on map
- Access via views: keySet(), values(), entrySet()
- Interface methods: put(k,v), get(k), remove(k), containsKey(k),
containsValue(v), putIfAbsent(k,v), computeIfAbsent(k,fn), merge(k,v,fn)
COMMON IMPLEMENTATIONS:
HashMap — O(1) average all key operations, no order
LinkedHashMap — O(1) operations, insertion order preserved
TreeMap — O(log n) operations, sorted by key,
adds: firstKey(), lastKey(), floorKey(), ceilingKey(), subMap()
ConcurrentHashMap — thread-safe, bucket-level locking, atomic compound ops
When to Use List, Set, or Map
This is the decision tree that every Java developer runs through when adding a new collection field.
START: What problem does this collection solve?
→ Need to remember position or order, and duplicates are natural?
Use LIST
Examples:
— Shopping cart items (same product can appear multiple times)
— Ordered search results (rank 1, rank 2, ...)
— A message history (same message can repeat)
— Steps in a pipeline (ordered execution)
→ Need to track whether something has been seen, with no duplicates?
Use SET
Examples:
— Visited URLs in a web crawler
— Unique tags on a blog post
— Processed order IDs to prevent double-processing
— Active user IDs in an online game
→ Need to look up a value by a meaningful key?
Use MAP
Examples:
— User ID → UserProfile (user session cache)
— Product SKU → Stock count (inventory)
— Word → Count (word frequency)
— City → Population (analytics lookup)
— Request path → Handler (routing table)
→ None of the above fit cleanly?
Combined structures are common:
— Map<String, List<String>> : tags grouped by category
— Map<String, Set<String>> : unique permissions per role
— Set<Map.Entry<K,V>> : same as Map.entrySet()
— List<Map<String, Object>> : rows in a query result
Core Differences — Head-to-Head Comparison
| Feature | List | Set | Map |
|---|---|---|---|
| Root interface | Collection<E> | Collection<E> | Map<K,V> (independent) |
| Element model | Single elements | Unique elements | Key-value pairs |
| Duplicates | Allowed | Rejected | Keys: unique; Values: any |
| Index access | Yes — get(int) | No | No (access by key) |
| Null elements | Allowed | 1 null (HashSet) | 1 null key (HashMap) |
| Primary operation | get(index), add() | contains(), add() | get(key), put() |
| Fastest impl | ArrayList O(1) get | HashSet O(1) contains | HashMap O(1) get |
| Ordered iteration | Always (insertion order) | Only LinkedHashSet/TreeSet | Only LinkedHashMap/TreeMap |
| Thread-safe option | CopyOnWriteArrayList | ConcurrentHashMap.newKeySet() | ConcurrentHashMap |
Core Operations with Examples
List — Ordered Access and Duplicate Handling
1// File: ListOperationsDemo.java
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class ListOperationsDemo {
7
8 public static void main(String[] args) {
9
10 List<String> searchHistory = new ArrayList<>();
11
12 // Order is preserved — duplicates allowed
13 searchHistory.add("laptop");
14 searchHistory.add("laptop stand");
15 searchHistory.add("laptop"); // duplicate — allowed in List
16 searchHistory.add("laptop bag");
17 searchHistory.add("laptop cooling pad");
18
19 System.out.println("=== List — ordered, duplicates allowed ===");
20 System.out.println("History : " + searchHistory);
21 System.out.println("Size : " + searchHistory.size()); // 5, not 4
22
23 // Index access — unique to List
24 System.out.println("get(0) : " + searchHistory.get(0)); // laptop
25 System.out.println("get(2) : " + searchHistory.get(2)); // laptop (duplicate)
26
27 // indexOf — finds first occurrence
28 System.out.println("indexOf(laptop) : " + searchHistory.indexOf("laptop"));
29 System.out.println("lastIndexOf(laptop) : " + searchHistory.lastIndexOf("laptop"));
30
31 // List as ordered pipeline — position carries semantic meaning
32 List<String> pipeline = new ArrayList<>();
33 pipeline.add("VALIDATE_INPUT");
34 pipeline.add("AUTHENTICATE");
35 pipeline.add("AUTHORISE");
36 pipeline.add("PROCESS");
37 pipeline.add("RESPOND");
38 System.out.println("\nExecution order: " + pipeline);
39 System.out.println("Step 3 is: " + pipeline.get(2)); // AUTHORISE
40 }
41}Output:
=== List — ordered, duplicates allowed ===
History : [laptop, laptop stand, laptop, laptop bag, laptop cooling pad]
Size : 5
get(0) : laptop
get(2) : laptop
indexOf(laptop) : 0
lastIndexOf(laptop) : 2
Execution order: [VALIDATE_INPUT, AUTHENTICATE, AUTHORISE, PROCESS, RESPOND]
Step 3 is: AUTHORISE
Set — Uniqueness and Membership Testing
1// File: SetOperationsDemo.java
2
3import java.util.Arrays;
4import java.util.HashSet;
5import java.util.List;
6import java.util.Set;
7
8public class SetOperationsDemo {
9
10 public static void main(String[] args) {
11
12 // Deduplication — first occurrence wins (via LinkedHashSet for order)
13 List<String> rawTags = Arrays.asList("java","spring","java","hibernate","spring","kafka");
14 Set<String> uniqueTags = new java.util.LinkedHashSet<>(rawTags);
15 System.out.println("=== Set — deduplication ===");
16 System.out.println("Raw tags : " + rawTags);
17 System.out.println("Unique tags : " + uniqueTags);
18 System.out.println("Size : " + uniqueTags.size()); // 4, not 6
19
20 System.out.println();
21
22 // Membership testing — O(1) for HashSet vs O(n) for List
23 Set<String> processedOrders = new HashSet<>();
24 processedOrders.add("ORD-001");
25 processedOrders.add("ORD-002");
26 processedOrders.add("ORD-003");
27
28 String incoming = "ORD-002";
29 System.out.println("=== Set — O(1) membership test ===");
30 System.out.println("Already processed ORD-002? " + processedOrders.contains(incoming));
31 System.out.println("Already processed ORD-099? " + processedOrders.contains("ORD-099"));
32
33 System.out.println();
34
35 // Set operations — intersection, difference, union
36 Set<String> userSkills = new HashSet<>(Set.of("Java","Spring","MySQL","Docker"));
37 Set<String> requiredSkills = new HashSet<>(Set.of("Java","Spring","Kubernetes","Redis"));
38
39 Set<String> matched = new HashSet<>(userSkills);
40 matched.retainAll(requiredSkills); // intersection
41
42 Set<String> missing = new HashSet<>(requiredSkills);
43 missing.removeAll(userSkills); // difference
44
45 System.out.println("=== Set operations — skill matching ===");
46 System.out.println("Matched : " + matched);
47 System.out.println("Missing : " + missing);
48 }
49}Output:
=== Set — deduplication ===
Raw tags : [java, spring, java, hibernate, spring, kafka]
Unique tags : [java, spring, hibernate, kafka]
Size : 4
=== Set — O(1) membership test ===
Already processed ORD-002? true
Already processed ORD-099? false
=== Set operations — skill matching ===
Matched : [Spring, Java]
Missing : [Kubernetes, Redis]
Map — Key-Based Lookup
1// File: MapOperationsDemo.java
2
3import java.util.HashMap;
4import java.util.Map;
5
6public class MapOperationsDemo {
7
8 public static void main(String[] args) {
9
10 // Map — key → value lookup
11 Map<String, Integer> wordCount = new HashMap<>();
12 String[] words = {"java","spring","java","hashmap","java","spring","hibernate"};
13
14 for (String word : words) {
15 wordCount.merge(word, 1, Integer::sum); // atomic increment
16 }
17
18 System.out.println("=== Map — word frequency ===");
19 wordCount.entrySet().stream()
20 .sorted(Map.Entry.<String,Integer>comparingByValue().reversed())
21 .forEach(e -> System.out.printf(" %-12s %d%n", e.getKey(), e.getValue()));
22
23 System.out.println();
24
25 // Map — O(1) lookup by key
26 Map<String, String> userRoles = new HashMap<>();
27 userRoles.put("priya@example.com", "ADMIN");
28 userRoles.put("rohan@example.com", "DEVELOPER");
29 userRoles.put("ananya@example.com", "VIEWER");
30
31 System.out.println("=== Map — O(1) role lookup ===");
32 String user = "priya@example.com";
33 System.out.println(user + " has role: " + userRoles.get(user));
34 System.out.println("unknown@x.com has role: " +
35 userRoles.getOrDefault("unknown@x.com", "GUEST"));
36
37 System.out.println();
38
39 // Map grouping — List as value type
40 Map<String, java.util.List<String>> cityToZones = new HashMap<>();
41 cityToZones.computeIfAbsent("Bengaluru", k -> new java.util.ArrayList<>())
42 .addAll(java.util.List.of("Koramangala","Whitefield","Indiranagar"));
43 cityToZones.computeIfAbsent("Mumbai", k -> new java.util.ArrayList<>())
44 .addAll(java.util.List.of("Andheri","Bandra","Powai"));
45
46 System.out.println("=== Map<String, List> — city zones ===");
47 cityToZones.forEach((city, zones) ->
48 System.out.println(" " + city + ": " + zones));
49 }
50}Output:
=== Map — word frequency ===
java 3
spring 2
hashmap 1
hibernate 1
=== Map — O(1) role lookup ===
priya@example.com has role: ADMIN
unknown@x.com has role: GUEST
=== Map<String, List> — city zones ===
Bengaluru: [Koramangala, Whitefield, Indiranagar]
Mumbai: [Andheri, Bandra, Powai]
Real-World Example — Flipkart Product Search Pipeline
A product search at Flipkart demonstrates all three interfaces working together. A List holds the ordered search result set (ranking matters). A Set tracks which product IDs have already been seen to prevent duplicates in merged results from multiple sources. A Map holds the full product details so each result can be enriched without repeated database lookups.
1// File: Product.java
2
3public record Product(
4 String productId,
5 String name,
6 String category,
7 double price,
8 double rating) {
9
10 @Override
11 public String toString() {
12 return String.format("[%s] %-28s Rs.%6.2f %.1f★ (%s)",
13 productId, name, price, rating, category);
14 }
15}1// File: SearchPipeline.java
2
3import java.util.ArrayList;
4import java.util.Comparator;
5import java.util.HashMap;
6import java.util.LinkedHashSet;
7import java.util.List;
8import java.util.Map;
9import java.util.Set;
10
11public class SearchPipeline {
12
13 // Map — product catalogue for O(1) enrichment by productId
14 private final Map<String, Product> catalogue = new HashMap<>();
15
16 public void indexProduct(Product product) {
17 catalogue.put(product.productId(), product);
18 }
19
20 // Simulate search from two sources (main index + sponsored)
21 public List<Product> search(String query, int maxResults) {
22
23 // Simulate raw result IDs from two sources — may overlap
24 List<String> mainResults = simulateMainSearch(query);
25 List<String> sponsoredResults = simulateSponsoredSearch(query);
26
27 // Set — deduplication: tracks seen product IDs, preserves first-seen order
28 Set<String> seenIds = new LinkedHashSet<>();
29 seenIds.addAll(mainResults);
30 seenIds.addAll(sponsoredResults); // duplicates from sponsored are silently rejected
31
32 // List — ordered enriched results: position matters for ranking
33 List<Product> results = new ArrayList<>();
34 for (String id : seenIds) {
35 Product product = catalogue.get(id); // Map: O(1) enrichment
36 if (product != null) {
37 results.add(product);
38 }
39 }
40
41 // Sort by rating descending, then price ascending for ties
42 results.sort(Comparator.comparingDouble(Product::rating).reversed()
43 .thenComparingDouble(Product::price));
44
45 // Return top maxResults as ordered List
46 return results.subList(0, Math.min(maxResults, results.size()));
47 }
48
49 private List<String> simulateMainSearch(String query) {
50 return List.of("P001", "P003", "P005", "P002");
51 }
52
53 private List<String> simulateSponsoredSearch(String query) {
54 return List.of("P004", "P001", "P003"); // P001, P003 are duplicates
55 }
56
57 public void printResults(List<Product> results, String query) {
58 System.out.println("=".repeat(70));
59 System.out.printf(" SEARCH RESULTS for: \"%s\" (%d results)%n",
60 query, results.size());
61 System.out.println("=".repeat(70));
62 for (int i = 0; i < results.size(); i++) {
63 System.out.printf(" %d. %s%n", i + 1, results.get(i));
64 }
65 System.out.println("=".repeat(70));
66 }
67
68 public static void main(String[] args) {
69
70 SearchPipeline pipeline = new SearchPipeline();
71
72 // Index products into Map<productId, Product>
73 pipeline.indexProduct(new Product("P001","Lenovo ThinkPad E14", "Laptops", 52999, 4.3));
74 pipeline.indexProduct(new Product("P002","Dell Inspiron 15", "Laptops", 48500, 4.1));
75 pipeline.indexProduct(new Product("P003","HP Pavilion 15", "Laptops", 44999, 4.4));
76 pipeline.indexProduct(new Product("P004","Asus VivoBook 15", "Laptops", 41999, 4.2));
77 pipeline.indexProduct(new Product("P005","Acer Aspire 5", "Laptops", 37999, 3.9));
78
79 // Execute search — internally uses List + Set + Map
80 List<Product> results = pipeline.search("laptop", 5);
81 pipeline.printResults(results, "laptop");
82
83 System.out.println();
84 System.out.println("Data structures used in this pipeline:");
85 System.out.println(" Map<String, Product> → product catalogue (O(1) enrichment)");
86 System.out.println(" Set<String> (LinkedHashSet) → deduplication across result sources");
87 System.out.println(" List<Product> (ArrayList) → ordered results by rank");
88 }
89}Output:
======================================================================
SEARCH RESULTS for: "laptop" (5 results)
======================================================================
1. [P003] HP Pavilion 15 Rs. 44999.00 4.4★ (Laptops)
2. [P001] Lenovo ThinkPad E14 Rs. 52999.00 4.3★ (Laptops)
3. [P004] Asus VivoBook 15 Rs. 41999.00 4.2★ (Laptops)
4. [P002] Dell Inspiron 15 Rs. 48500.00 4.1★ (Laptops)
5. [P005] Acer Aspire 5 Rs. 37999.00 3.9★ (Laptops)
======================================================================
Data structures used in this pipeline:
Map<String, Product> → product catalogue (O(1) enrichment)
Set<String> (LinkedHashSet) → deduplication across result sources
List<Product> (ArrayList) → ordered results by rank
Performance Considerations
| Operation | List (ArrayList) | Set (HashSet) | Map (HashMap) |
|---|---|---|---|
| Add element | O(1) amortised | O(1) average | O(1) average |
| Access by index/key | O(1) | N/A | O(1) average |
| Contains/containsKey | O(n) | O(1) average | O(1) average |
| Remove by value/key | O(n) | O(1) average | O(1) average |
| Iteration | O(n) | O(n + capacity) | O(n + capacity) |
| Sorted iteration | O(n log n) extra | Use TreeSet | Use TreeMap |
| Null support | Yes | 1 null (HashSet) | 1 null key (HashMap) |
| Duplicate handling | Allows all | Rejects all | Keys unique, values any |
The O(n) contains trap: list.contains(element) scans every element linearly. For a list of 100,000 items, this is 100,000 comparisons per call. set.contains(element) or map.containsKey(key) is one or two comparisons per call. A very common production performance improvement is identifying a repeated list.contains() pattern and replacing the List with a Set or Map.
Best Practices
Start with the interface, not the implementation. Declare List<String> results = new ArrayList<>(), not ArrayList<String> results = new ArrayList<>(). This makes it trivial to swap implementations — ArrayList to LinkedList, HashSet to LinkedHashSet for ordered output — without changing any other code.
Choose Set over List for any "have I seen this?" pattern. If the code repeatedly checks list.contains(item) before deciding whether to add, that is O(n) per check — and the real intent is uniqueness, not order. A Set handles both deduplication and O(1) membership testing in one structure.
Use Map over parallel lists. Two lists maintained in lockstep — List<String> keys and List<Value> values where keys.get(i) corresponds to values.get(i) — are almost always a design smell. A Map<String, Value> is cleaner, faster on lookup, and impossible to corrupt by inserting in the wrong list.
Use Map<K, List<V>> for grouping. computeIfAbsent(key, k -> new ArrayList<>()).add(value) is the standard pattern for building a grouped index: category to product list, user to order list, date to event list. The inner List preserves insertion order within each group; the outer Map gives O(1) group lookup.
Common Mistakes
Mistake 1 — Using List.contains() in a Loop When Set Is Needed
1List<String> processedIds = new ArrayList<>();
2
3// WRONG — O(n) per contains(), total is O(n²) for n incoming IDs
4for (String incomingId : thousandsOfIncomingIds) {
5 if (!processedIds.contains(incomingId)) { // scans all of processedIds
6 processedIds.add(incomingId);
7 process(incomingId);
8 }
9}
10
11// CORRECT — O(1) per add(), total is O(n)
12Set<String> processedIds2 = new HashSet<>();
13for (String incomingId : thousandsOfIncomingIds) {
14 if (processedIds2.add(incomingId)) { // add() returns false for duplicates
15 process(incomingId);
16 }
17}Mistake 2 — Using a List of Pairs When Map Is the Correct Model
1// WRONG — parallel lists that must stay in sync
2List<String> configKeys = new ArrayList<>();
3List<String> configValues = new ArrayList<>();
4configKeys.add("db.host"); configValues.add("localhost");
5configKeys.add("db.port"); configValues.add("5432");
6
7// Lookup requires manual scanning — O(n) and error-prone
8int idx = configKeys.indexOf("db.port");
9String port = configValues.get(idx); // breaks if lists are out of sync
10
11// CORRECT — Map models the key→value relationship directly
12Map<String, String> config = new HashMap<>();
13config.put("db.host", "localhost");
14config.put("db.port", "5432");
15String port2 = config.get("db.port"); // O(1), no sync concernsMistake 3 — Using Set When Order Must Be Preserved
1// WRONG — HashSet has no order guarantee
2Set<String> steps = new HashSet<>();
3steps.add("Step 1: Validate");
4steps.add("Step 2: Process");
5steps.add("Step 3: Respond");
6
7for (String step : steps) {
8 System.out.println(step); // may print Step 3 before Step 1 — order undefined!
9}
10
11// CORRECT for uniqueness + insertion order: LinkedHashSet
12Set<String> orderedSteps = new java.util.LinkedHashSet<>();
13orderedSteps.add("Step 1: Validate");
14orderedSteps.add("Step 2: Process");
15orderedSteps.add("Step 3: Respond");
16// Iterates in insertion order — always Step 1, Step 2, Step 3Mistake 4 — Using Map When the Value IS the Key (Should Be a Set)
1// WRONG — Map used purely to track presence, value adds no information
2Map<String, Boolean> visitedUrls = new HashMap<>();
3visitedUrls.put("https://site.com/page1", Boolean.TRUE);
4visitedUrls.put("https://site.com/page2", Boolean.TRUE);
5
6if (visitedUrls.containsKey("https://site.com/page1")) { ... }
7
8// CORRECT — Set is the right model: membership is the only question
9Set<String> visitedUrls2 = new HashSet<>();
10visitedUrls2.add("https://site.com/page1");
11visitedUrls2.add("https://site.com/page2");
12
13if (visitedUrls2.contains("https://site.com/page1")) { ... }Interview Questions
Q1. What is the difference between List, Set, and Map in Java?
List is an ordered, indexed sequence that allows duplicates — get(index) retrieves any element in O(1) (ArrayList). Set stores unique elements — no duplicates — with O(1) membership testing (HashSet); contains() on a Set is O(1) versus O(n) on a List. Map stores key-value pairs where keys are unique — get(key) retrieves any value in O(1) average (HashMap). The choice depends on the question your data needs to answer: "what is at position i?" → List; "have I seen this element?" → Set; "what is the value for this key?" → Map.
Q2. When should you use a Set instead of a List in Java?
Use a Set instead of a List when uniqueness is required and positional access by index is not needed. The critical performance signal: if the code calls list.contains(element) before adding (checking for duplicates), it is O(n) per check and O(n²) total. A Set handles uniqueness automatically — set.add(element) returns false for duplicates in O(1). Additional cases: membership testing (set.contains() is O(1) vs list.contains() O(n)), set algebra operations (union via addAll(), intersection via retainAll(), difference via removeAll()), and deduplication pipelines where first-occurrence order matters (use LinkedHashSet).
Q3. What is the difference between HashMap and HashSet internally?
HashSet is literally a thin wrapper around HashMap. When you call set.add(element), it internally calls map.put(element, PRESENT) where PRESENT is a shared static dummy Object. set.contains(element) calls map.containsKey(element). The entire HashSet implementation delegates to HashMap — same hash table, same bucket structure, same treeification at 8 entries. This is why both have the same O(1) average complexity and why both require equals() and hashCode() to be correctly implemented on elements/keys.
Q4. Can a Map be iterated like a List or Set?
Map does not implement Iterable directly. To iterate, use one of three views: map.keySet() returns a Set<K> of all keys; map.values() returns a Collection<V> of all values; map.entrySet() returns a Set<Map.Entry<K,V>> of all key-value pairs. entrySet() is the most efficient for accessing both key and value — it avoids the second hash lookup that keySet() + map.get(key) would require. map.forEach((k, v) -> ...) is the cleanest Java 8+ iteration pattern.
Q5. How do you choose between LinkedHashSet and TreeSet?
Both maintain order alongside uniqueness, but of different kinds. LinkedHashSet preserves insertion order — elements iterate in the order add() was first called, O(1) operations. TreeSet maintains sorted natural order (or Comparator order) — elements always iterate alphabetically or numerically, O(log n) operations. Use LinkedHashSet when "first seen" order or display consistency matters. Use TreeSet when sorted order is required, or when range operations (headSet(), tailSet(), floor(), ceiling()) are needed.
Q6. What is Map.Entry and when do you use it?
Map.Entry<K, V> is the interface representing a single key-value pair inside a Map. It is obtained by iterating map.entrySet(). entry.getKey() and entry.getValue() give both the key and value in one step — no second hash lookup. The entry.setValue(newValue) method updates the value in place within the live map. Map.Entry is also useful for sorting: Map.Entry.comparingByValue() gives a Comparator that sorts entries by value, enabling patterns like "top 5 words by frequency" via stream().sorted(Map.Entry.comparingByValue().reversed()).limit(5).
FAQs
What is the simplest rule for choosing between List, Set, and Map?
Three questions: Does order or position matter? → List. Does the data need to be unique? → Set. Does each item have a key you will look up by? → Map. Most data models fall cleanly into one of these. When multiple requirements exist — unique AND ordered, or key-lookup AND grouped — combine them: LinkedHashSet for unique+ordered, Map<K, List<V>> for grouped key-lookup.
Is Map a subtype of Collection in Java?
No. Map<K, V> is a completely separate hierarchy from Collection<E>. Collection contains elements. Map contains key-value pairs — a fundamentally different abstraction. You cannot pass a Map where a Collection is expected. The three views map.keySet(), map.values(), and map.entrySet() return Collection or Set objects that bridge between the two hierarchies.
Can a List contain duplicate elements?
Yes. List explicitly allows duplicate elements at any position. list.add("A"); list.add("A"); results in a list of size 2 with "A" at both index 0 and index 1. This contrasts with Set, which silently rejects the second add("A") and remains size 1. If you want a sequence that rejects duplicates, use LinkedHashSet (preserves order) or TreeSet (sorts).
What happens when you add a duplicate to a Set?
set.add(element) returns false and the Set is unchanged — no exception is thrown. The element is considered a duplicate when equals() returns true for an existing element. For TreeSet, the comparison uses compareTo() or the supplied Comparator instead. The set size does not increase and the existing element is not replaced.
What is the difference between Map.put() return value and Set.add() return value?
Set.add(element) returns boolean — true if the element was new, false if it was a duplicate. Map.put(key, value) returns V — the previous value associated with the key, or null if the key was absent. Both return values are meaningful and often underused. if (set.add(item)) is cleaner than if (!set.contains(item)) { set.add(item); }. String old = map.put(key, newValue) gives you the previous value for logging or undo without a separate get() call.
How do you convert a List to a Set and back?
Set<E> set = new HashSet<>(list) deduplicates in O(n) — no order preservation. Set<E> ordered = new LinkedHashSet<>(list) deduplicates while preserving first-occurrence order. List<E> back = new ArrayList<>(set) converts back to a List. The idiomatic one-liner for deduplication with order: new ArrayList<>(new LinkedHashSet<>(list)).
Summary
List, Set, and Map answer three distinct questions about data. List asks "what is at position i?" — ordered, indexed, duplicates allowed, ArrayList for most cases. Set asks "have I seen this before?" — unique elements, O(1) membership, HashSet for speed, LinkedHashSet for order, TreeSet for sorted access. Map asks "what is the value for key X?" — key-value pairs, O(1) lookup, HashMap for most cases, LinkedHashMap for insertion-order iteration, TreeMap for sorted keys.
The most impactful collection choice in production code is usually switching list.contains() inside a loop to a Set — that single change turns O(n²) into O(n). The second most common is replacing parallel lists with a Map. Get the choice right at design time and the implementation details follow naturally.
For interviews: know the exact API difference between each interface (get(index) on List, contains(element) on Set, get(key) on Map), explain that HashSet is backed by HashMap, describe when LinkedHashSet vs TreeSet is appropriate, and be ready to explain the grouping pattern Map<K, List<V>>. These questions distinguish developers who understand data structure design from those who simply know syntax.
What to Read Next
| Topic | Link |
|---|---|
| How ArrayList implements the List interface with a resizable array | Java ArrayList → |
| How HashSet uses HashMap internally to enforce element uniqueness | Java HashSet → |
| How HashMap achieves O(1) key lookup with a hash table and bucket array | Java HashMap → |
| How the Collections Framework organises all interfaces and implementations | Java Collections Framework → |
| How Generics make List, Set, and Map type-safe at compile time | Java Generics → |