Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Asynchronous Patterns

Modern non-blocking concurrency with Futures and async/await.

Futures and Promises represent values that don’t exist yet, enabling asynchronous computation without blocking threads.

Diagram

Understanding the difference is crucial!

Diagram

CompletableFuture is Java’s modern way to handle asynchronous operations.

CompletableFutureBasics.java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureBasics {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Create with supplyAsync (returns value)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Hello";
});
// Create with runAsync (no return value)
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("Running async task");
});
// Get result (blocks until ready)
String result = future1.get();
System.out.println("Result: " + result);
}
}
  • thenApply: Transforms result synchronously (returns value)
  • thenCompose: Chains another CompletableFuture (returns Future)
Diagram
CompletableFutureChaining.java
import java.util.concurrent.CompletableFuture;
public class CompletableFutureChaining {
public static void main(String[] args) {
// Chain operations
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World") // Synchronous transformation
.thenApply(String::toUpperCase); // Another transformation
future.thenAccept(System.out::println); // Consume result
// thenCompose for async chaining
CompletableFuture<String> future2 = CompletableFuture
.supplyAsync(() -> "user123")
.thenCompose(userId -> fetchUserData(userId)); // Returns Future
// Exception handling
CompletableFuture<String> future3 = CompletableFuture
.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Error!");
}
return "Success";
})
.exceptionally(ex -> "Handled: " + ex.getMessage()) // Handle exception
.thenApply(s -> "Result: " + s);
}
static CompletableFuture<String> fetchUserData(String userId) {
return CompletableFuture.supplyAsync(() -> {
// Simulate async fetch
return "Data for " + userId;
});
}
}
  • allOf: Wait for all futures to complete
  • anyOf: Wait for any future to complete
CombiningFutures.java
import java.util.concurrent.CompletableFuture;
import java.util.Arrays;
public class CombiningFutures {
public static void main(String[] args) {
// Create multiple futures
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Result 3");
// Wait for all
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);
allFutures.thenRun(() -> {
System.out.println("All completed!");
});
// Wait for any
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);
anyFuture.thenAccept(result -> {
System.out.println("First result: " + result);
});
// Combine two futures
CompletableFuture<String> combined = future1.thenCombine(future2,
(r1, r2) -> r1 + " + " + r2);
}
}

Python’s asyncio provides async/await syntax for asynchronous programming.

The event loop manages and executes asynchronous tasks.

Diagram
asyncio_basics.py
import asyncio
async def fetch_data(url):
"""Async function (coroutine)"""
await asyncio.sleep(1) # Simulate I/O
return f"Data from {url}"
async def main():
# Run coroutines concurrently
results = await asyncio.gather(
fetch_data("url1"),
fetch_data("url2"),
fetch_data("url3")
)
print(results)
# Run event loop
asyncio.run(main())

concurrent.futures.Future vs asyncio.Future

Section titled “concurrent.futures.Future vs asyncio.Future”
future_types.py
from concurrent.futures import ThreadPoolExecutor, Future as ThreadFuture
import asyncio
# ThreadPoolExecutor Future
def sync_task():
return "Result"
with ThreadPoolExecutor() as executor:
thread_future = executor.submit(sync_task)
result = thread_future.result() # Blocks
# asyncio Future
async def async_task():
await asyncio.sleep(1)
return "Result"
async def main():
asyncio_future = asyncio.create_task(async_task())
result = await asyncio_future # Non-blocking
print(result)
asyncio.run(main())

FeatureJavaPython
Future CreationCompletableFuture.supplyAsync()asyncio.create_task() or executor.submit()
ChainingthenApply(), thenCompose()await in coroutines
CombiningallOf(), anyOf()asyncio.gather(), asyncio.wait()
Exception Handlingexceptionally(), handle()try/except in coroutines
Event LoopImplicit (ForkJoinPool)Explicit (asyncio.run())

AsyncAPIClient.java
import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.stream.Collectors;
public class AsyncAPIClient {
public CompletableFuture<String> fetchUser(String userId) {
return CompletableFuture.supplyAsync(() -> {
// Simulate API call
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "User: " + userId;
});
}
public CompletableFuture<List<String>> fetchUsers(List<String> userIds) {
List<CompletableFuture<String>> futures = userIds.stream()
.map(this::fetchUser)
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
}

Q1: “What’s the difference between thenApply and thenCompose?”

Section titled “Q1: “What’s the difference between thenApply and thenCompose?””

Answer:

  • thenApply: Synchronous transformation, takes value, returns value
  • thenCompose: Asynchronous chaining, takes value, returns Future
  • Use thenApply: For simple transformations
  • Use thenCompose: When you need to chain another async operation

Q2: “When would you use async/await vs threads in Python?”

Section titled “Q2: “When would you use async/await vs threads in Python?””

Answer:

  • async/await: I/O-bound concurrent operations, many connections, event-driven
  • threads: CPU-bound tasks, simpler I/O scenarios, when you need OS-level parallelism
  • Choose: Based on task type and concurrency requirements


Mastering asynchronous patterns enables efficient non-blocking systems! ⚡