Skip to Content
Course content

246: Common Java Interview Questions on Collections and Concurrency

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, I've sat on both sides of the interview table. When an interviewer asks about Collections and Concurrency, they aren't just checking if you've memorized the API docs; they want to know if you understand how Java actually behaves when multiple threads start fighting over the same piece of memory. Here are the questions that usually trip people up and how I'd explain the answers.

Why bother with ConcurrentHashMap over a synchronized map?

If you're in an interview and they ask about thread-safe maps, don't just say "use a synchronized map." That's a rookie mistake. Collections.synchronizedMap() wraps the entire map in a single global lock. It's like having a massive library where only one person is allowed inside at a time, regardless of which book they want. It's safe, but it's a performance nightmare.

ConcurrentHashMap is smarter. It uses a technique called lock striping (or in newer Java versions, a mix of CAS operations and synchronized blocks on individual bucket nodes). It essentially breaks the map into segments so that multiple threads can write to different parts of the map simultaneously. If Thread A is updating a key in bucket 1, Thread B can still update a key in bucket 10 without waiting.

// This is the "slow" way - global lock
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// This is the "pro" way - high concurrency
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();


What's actually happening during a ConcurrentModificationException?

You've probably seen this error while trying to remove an item from a list while iterating over it. This is what we call a "fail-fast" iterator. The ArrayList iterator keeps a internal modCount. If the list is modified structurally while the iterator is active, the iterator sees that the count has changed and immediately throws the exception to prevent unpredictable behavior.

Now, if you absolutely need to modify a list while iterating—say, you're cleaning up expired cache entries in a background thread—you have two real options. Use the Iterator.remove() method, or switch to a "fail-safe" (or more accurately, weakly consistent) collection like CopyOnWriteArrayList. The latter creates a fresh copy of the underlying array every time you modify it, so the iterator is just looking at a "snapshot" of the data from the moment it was created.

// This will crash
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
    if (s.equals("B")) list.remove(s); // Boom! ConcurrentModificationException
}

// This is safe because it works on a snapshot
List<String> cowList = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : cowList) {
    if (s.equals("B")) cowList.remove(s); // Works fine
}


When should I use a Callable instead of a Runnable?

I like to think of a Runnable as "fire and forget." You tell the thread to go do some work, and that's it. It can't return a value, and if it throws a checked exception, you have to handle it inside the run() method because the method signature doesn't allow it.

A Callable, on the other hand, is for when you actually need a result back. It returns a Future. Imagine you're building a dashboard that needs to fetch data from three different APIs. You wouldn't use Runnable; you'd use Callable so you can wait for the results and then aggregate them.

Callable<Integer> fetchPrice = () -> {
    // Simulate network delay
    Thread.sleep(1000);
    return 150; // Return a value!
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(fetchPrice);

// This blocks until the result is ready
Integer price = future.get(); 


Is the 'volatile' keyword enough to make a variable thread-safe?

Short answer: No. Long answer: volatile only solves the visibility problem, not the atomicity problem.

In Java, threads often cache variables in CPU registers for performance. If Thread A updates a variable, Thread B might keep reading the old cached value. Marking a variable volatile tells Java: "Don't cache this; always read and write directly to main memory."

But here is the trap: if you do count++ on a volatile variable, you aren't doing one operation. You're doing three: read, increment, and write. If two threads do this at the exact same time, they might both read '5', both increment it to '6', and both write '6' back. You just lost an update. For that, you need AtomicInteger or a synchronized block.




📋 Practical Task

Exercise: Building a Thread-Safe User Session Registry

You are tasked with creating a UserSessionRegistry that allows a high-traffic web server to track active users. The registry must support the following requirements:

  • Concurrent Access: Multiple threads must be able to add and remove users simultaneously without locking the entire registry.
  • Safe Cleanup: A background "cleanup" thread must be able to iterate through all active sessions and remove those that have expired without triggering a ConcurrentModificationException.
  • Result Tracking: Implement a method that uses a Callable to calculate the total number of active sessions across multiple distributed shards (simulated by summing a few different maps).

Your Task: Implement the UserSessionRegistry class. Choose the correct java.util.concurrent collection to store the sessions and use an ExecutorService to handle the Callable logic for the session count.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.