Skip to content
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.

Future lifecycle: submit returns a pending Future immediately, the task runs in the background, and get() returns the completed result

Understanding the difference is crucial!

Blocking I/O leaves the thread waiting for the result, while non-blocking I/O lets it continue working and get notified when ready

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)
CompletableFuture thenApply synchronously transforms a String to its length, while thenCompose chains another async fetchData call
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.

Python asyncio event loop managing a task queue, I/O-waiting and ready tasks: run ready tasks, suspend on I/O, resume when I/O is ready
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! ⚡