E‑commerce Concurrency Case Study: 12 Interview Questions with Answers

This article provides a self‑test for interview preparation, presenting twelve common interview questions and detailed answers on an e‑commerce concurrency case, covering serial vs async performance, CompletableFuture.allOf, ConcurrentHashMap.compute atomicity, layered cache design, Semaphore rate limiting, ReentrantLock fairness, AtomicInteger CAS, exceptional fallback, double‑layer protection, concurrency safety testing, thread‑pool configuration, and precise cache invalidation.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
E‑commerce Concurrency Case Study: 12 Interview Questions with Answers

Q1 Serial vs Async Performance

Serial version calls four interfaces sequentially, total time 530 ms:

ProductInfo product = productClient.getProduct(productId);
PriceInfo price = productClient.getPrice(productId);
StockInfo stock = productClient.getStock(productId);
List<RecommendInfo> recs = productClient.getRecommendations(userId, productId);

Async version submits product, price, and stock calls to a thread pool and waits with CompletableFuture.allOf, reducing total time to the slowest call (150 ms). Recommendation is fetched separately because it depends on userId.

CompletableFuture<ProductInfo> productF = CompletableFuture.supplyAsync(() -> productClient.getProduct(productId), executor);
CompletableFuture<PriceInfo> priceF = CompletableFuture.supplyAsync(() -> productClient.getPrice(productId), executor);
CompletableFuture<StockInfo> stockF = CompletableFuture.supplyAsync(() -> productClient.getStock(productId), executor)
    .exceptionally(ex -> StockInfo.DEFAULT);
CompletableFuture<Void> allDone = CompletableFuture.allOf(productF, priceF, stockF);
allDone.join();

Three independent calls run in parallel. CompletableFuture.allOf waits for all to finish.

Total latency becomes the maximum of the three (150 ms) instead of the sum (530 ms).

Recommendation data is not cached because it is user‑specific and must be queried in real time.

Performance results:

Serial: 530 ms (four calls sequentially).

Concurrent (cache miss): 200 ms (three calls parallel + recommendation 200 ms).

Cache hit: <1 ms for product info (ConcurrentHashMap returns directly).

Improvement: >2.5× faster; thread‑pool reuse avoids thread creation overhead.

Q2 CompletableFuture.allOf

CompletableFuture.allOf

creates a new CompletableFuture that completes only when all supplied futures complete.

CompletableFuture<ProductInfo> productF = ...;
CompletableFuture<PriceInfo> priceF = ...;
CompletableFuture<StockInfo> stockF = ...;
CompletableFuture<Void> allDone = CompletableFuture.allOf(productF, priceF, stockF);
allDone.join(); // blocks until all are done

Compared with Thread.join():

Asynchronous composition vs synchronous blocking.

Supports graceful degradation via exceptionally instead of try‑catch.

Provides result transformation with thenApply and rich composition (anyOf, thenCombine).

Thread‑pool management reuses threads and does not occupy the caller thread.

Q3 ConcurrentHashMap.compute Atomicity

ConcurrentHashMap.compute

guarantees that the entire lambda executes atomically for a given key. Only one thread can run the lambda, preventing duplicate remote calls.

return productCache.compute(productId, (k, oldValue) -> {
    if (oldValue != null && !isExpired(oldValue)) {
        return oldValue; // reuse cached value
    }
    CompletableFuture<ProductInfo> productF = ...;
    CompletableFuture<PriceInfo> priceF = ...;
    CompletableFuture<StockInfo> stockF = ...;
    CompletableFuture.allOf(productF, priceF, stockF).join();
    ProductBaseInfo info = new ProductBaseInfo(productF.join(), priceF.join(), stockF.join());
    info.cacheTime = System.currentTimeMillis();
    return info; // atomic write back
});

Using putIfAbsent only atomically checks existence and inserts; it cannot prevent multiple threads from performing the expensive remote calls, leading to cache penetration. With compute, 20 concurrent threads cause only one actual remote fetch (three calls) while the others read the already computed value.

Q4 Layered Cache Strategy

Cache decisions are based on data characteristics.

Product, price, stock info – cached by productId because the data is identical for all users and has a high hit rate. Cache is invalidated precisely when inventory changes.

Recommendation info – not cached because it is personalized per userId, changes frequently, and the recommendation service already maintains its own cache.

// Get shared product base info from cache
ProductBaseInfo baseInfo = getOrComputeProductBase(productId);
// Real‑time recommendation (no cache)
List<RecommendInfo> recs = productClient.getRecommendations(userId, productId);

Q5 Semaphore Rate Limiting

Semaphore is built on AQS shared mode and limits the number of concurrent threads that can enter a critical section.

private final Semaphore inventorySemaphore = new Semaphore(10); // max 10 concurrent deductions
boolean acquired = inventorySemaphore.tryAcquire(acquireTimeoutSeconds, TimeUnit.SECONDS);
if (!acquired) {
    throw new IllegalStateException("System busy, please try later");
}
// ... critical section ...
finally {
    if (acquired) {
        inventorySemaphore.release(); // return permit
    }
}
tryAcquire(timeout)

waits up to the specified time and returns false on timeout; acquire() blocks indefinitely and is unsuitable for user‑facing interfaces. tryAcquire() without timeout returns immediately for fast‑fail scenarios.

Q6 ReentrantLock Fair Lock

private final ReentrantLock stockLock = new ReentrantLock(true); // fair lock

Fair lock guarantees FIFO acquisition order, preventing thread starvation at the cost of slightly lower throughput. In inventory deduction, fairness is preferred because the operation involves money and user rights.

stockLock.lock();
try {
    AtomicInteger stock = stockMap.get(productId);
    if (stock == null || stock.get() < quantity) {
        return false;
    }
    stock.getAndAdd(-quantity);
    return true;
} finally {
    stockLock.unlock();
}

Q7 AtomicInteger CAS Decrement

AtomicInteger stock = stockMap.get(productId);
stock.getAndAdd(-quantity); // atomic decrement, returns previous value

CAS (Compare‑And‑Swap) reads the current value, compares it with the expected value, and updates it atomically using the CPU cmpxchg instruction.

Q8 Service Degradation with exceptionally

CompletableFuture<StockInfo> stockF = CompletableFuture.supplyAsync(
    () -> productClient.getStock(productId), executor)
    .exceptionally(ex -> {
        System.out.println("Stock query failed, using default: " + ex.getMessage());
        return StockInfo.DEFAULT; // fallback with available = 0
    });
StockInfo.DEFAULT

is defined as:

public static final StockInfo DEFAULT = new StockInfo(null, 0);

When the stock service fails, product and price still display, stock shows “temporarily unavailable”, and page availability improves from 0 % to about 75 %.

Degradation returns fallback data; circuit‑breaker (e.g., Sentinel) is not implemented in this example.

Q9 Double‑Layer Protection

Inventory deduction uses two layers:

// Layer 1: Semaphore – limits overall concurrency
if (!inventorySemaphore.tryAcquire(...)) { throw ...; }

// Layer 2: ReentrantLock – guarantees atomic check‑then‑deduct
stockLock.lock();
try {
    AtomicInteger stock = stockMap.get(productId);
    if (stock == null || stock.get() < quantity) return false;
    stock.getAndAdd(-quantity);
    return true;
} finally {
    stockLock.unlock();
}

Only Semaphore cannot prevent overselling because it does not protect the check‑then‑deduct sequence.

Only ReentrantLock can cause high contention under heavy load.

Both together provide system‑level protection (rate limiting) and business‑level correctness (no oversell).

Q10 Concurrency Safety Test

Test verifies that 20 threads querying the same product cause only one compute execution.

@Test
@DisplayName("Concurrency safety: multiple threads query same product, cache writes once")
void testConcurrentCache_sameProduct() throws InterruptedException {
    int threadCount = 20;
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch doneLatch = new CountDownLatch(threadCount);
    AtomicInteger invokeCount = new AtomicInteger(0);

    ProductClient countingClient = new CountingProductClient(invokeCount);
    ProductDetailAsyncService countingService = new ProductDetailAsyncService(countingClient, executor);

    for (int i = 0; i < threadCount; i++) {
        executor.submit(() -> {
            try {
                startLatch.await();
                countingService.getOrCompute(1L, 100L);
            } finally {
                doneLatch.countDown();
            }
        });
    }
    startLatch.countDown(); // fire
    doneLatch.await(10, TimeUnit.SECONDS);

    assertTrue(invokeCount.get() <= 6,
        "Concurrent queries of same product, actual invoke count " + invokeCount.get() + " should be <= 6");
}

Key points: CountDownLatch synchronizes thread start; CountingProductClient counts remote calls; assertion allows a small extra count due to race, confirming that only one thread performed the expensive computation.

Q11 Thread‑Pool Parameter Configuration

executor = new ThreadPoolExecutor(
    10,                     // corePoolSize
    20,                     // maximumPoolSize
    60L, TimeUnit.SECONDS, // keepAliveTime for non‑core threads
    new ArrayBlockingQueue<>(100), // bounded work queue
    new TestThreadFactory("detail-async-test"),
    new ThreadPoolExecutor.CallerRunsPolicy() // back‑pressure
);

Behavior:

If core threads are not full, a new core thread is created.

If core threads are full, tasks are queued up to 100.

If the queue is full, non‑core threads are created up to the maximum of 20.

If both threads and queue are exhausted, the caller runs the task, providing throttling.

Using Executors.newFixedThreadPool creates an unbounded LinkedBlockingQueue, which can cause OOM; explicit ThreadPoolExecutor allows a bounded queue and custom rejection policy.

Q12 Precise Cache Invalidation and Expired Cleanup

// Precise invalidation (O(1))
public void invalidateProductCache(Long productId) {
    productCache.remove(productId);
}

// Batch cleanup of expired entries
public int cleanExpiredCache() {
    AtomicInteger removed = new AtomicInteger(0);
    productCache.entrySet().removeIf(entry -> {
        if (isExpired(entry.getValue())) {
            removed.incrementAndGet();
            return true;
        }
        return false;
    });
    return removed.get();
}

private boolean isExpired(ProductBaseInfo info) {
    return System.currentTimeMillis() - info.cacheTime > CACHE_TTL_MS; // TTL = 5 minutes
}

API summary: invalidateProductCache(productId) – O(1) precise removal. cleanExpiredCache() – O(n) batch removal of stale entries. clearCache() – O(1) clear all entries. cacheSize() – O(1) query current size.

This example uses ConcurrentHashMap to illustrate core concurrency primitives; in production, libraries such as Caffeine or Guava Cache provide richer features like LRU eviction, automatic loading, and statistics.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavacacheconcurrencythreadpoolCompletableFuturesemaphorereentrantlockatomicinteger
CodeSmart Hoops
Written by

CodeSmart Hoops

A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.