← Google Interview Insights

Google·Software Engineer·Onsite - System Design / Architecture·Senior

SeniorPrefer not to say
Jul 2026

Summary

Google SWE interview that went deep into concurrency internals. The core ask was building a future/promise abstraction from scratch and then using it to parallelize array processing, no CompletableFuture allowed. Harder than it sounds when you're on the spot.

Questions Asked (6)

Q1

Design and implement an AsyncFuture<T> class from scratch in Java, including the completion state machine, thread-safe callback registration, and composition operators like thenApply, thenCompose, and thenCombine. You cannot use CompletableFuture.

System DesignTechnical Trade-offsAlgorithms & Data Structures
Author's notes

This was the main event and it ate most of the session.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints, then design a minimal state machine with a volatile result and a lock-free callback list. Implement core methods (complete, whenComplete, thenApply) first, then build composition operators on top, explaining thread-safety and exception handling at each step.

Pro tip: Emphasize that you would use a lock-free approach with CAS for the completion state and a concurrent queue for callbacks to avoid blocking, and discuss how you'd handle exceptions and cancellation to show production-level thinking.

1. Clarify Requirements and Constraints

Ask about expected usage, thread-safety guarantees, and whether cancellation or timeouts are needed. Confirm that no external libraries like CompletableFuture can be used.

2. Design the Core State Machine

Define states: PENDING, COMPLETED, and possibly CANCELLED. Use an AtomicReference or volatile field for the result and a lock-free mechanism to transition states.

3. Implement Thread-Safe Callback Registration

Use a concurrent data structure (e.g., ConcurrentLinkedQueue) to store callbacks. Ensure that callbacks registered after completion are executed immediately, and those before are executed upon completion.

4. Implement Composition Operators

Build thenApply, thenCompose, and thenCombine by creating new AsyncFuture instances that depend on the completion of the source futures. Handle exceptions and propagate them to the derived futures.

5. Discuss Trade-offs and Edge Cases

Talk about memory visibility, potential race conditions, and performance implications. Mention how you would test the implementation for correctness under concurrency.

Key Points to Mention

  • Use of volatile or AtomicReference for the result to ensure visibility across threads.
  • Lock-free callback management with a concurrent queue and atomic state transitions.
  • Exception propagation: how errors are captured and passed to dependent futures.
  • Composition operators: thenApply for transformation, thenCompose for flat-mapping, thenCombine for combining two futures.
  • Handling of callbacks registered after completion (immediate execution).
  • Potential for stack overflow with recursive composition and how to mitigate (e.g., trampolining).

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

What clarifying questions would you ask before diving into implementing an async future and a parallel array-processing routine on top of it?

System DesignTechnical Trade-offsAdaptability & Ambiguity
Author's notes

I actually did okay here.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by acknowledging that the question tests your ability to uncover requirements before coding. Organize your clarifying questions into categories: functional requirements, performance/scalability, error handling, and integration. Then, briefly explain how the answers would shape your design decisions.

Pro tip: Show that you think about trade-offs by asking about constraints like latency, throughput, and resource limits. Also, mention that you'd clarify the expected behavior under failure and cancellation, as these are often overlooked in async systems.

1. Clarify Functional Requirements

Ask what the async future and parallel array-processing routine are supposed to achieve. For example, what operations are performed on the array elements, and what is the expected output?

2. Understand Performance and Scalability Needs

Inquire about expected input size, latency requirements, throughput, and whether the system needs to scale horizontally. This determines the choice of async model and parallelism strategy.

3. Explore Error Handling and Cancellation

Ask how errors should be handled (e.g., fail-fast, retry, partial results) and whether cancellation or timeouts are required. This affects the design of the future and the parallel routine.

4. Determine Integration and Environment Constraints

Clarify the programming language, existing frameworks, and how this component will integrate with other services. Also, ask about resource limits (CPU, memory, I/O).

5. Confirm Testing and Observability Requirements

Ask about testing expectations, logging, metrics, and tracing. This ensures the implementation is maintainable and debuggable in production.

Key Points to Mention

  • Concurrency model: threads, event loop, or coroutines?
  • Data dependencies and ordering guarantees
  • Backpressure and flow control
  • Resource utilization and limits
  • Error propagation and recovery strategies
  • Cancellation and timeout semantics

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Using your AsyncFuture primitive, implement a parallelProcess routine that splits a large int array into chunks, processes each chunk concurrently, and folds the results into a single future without blocking any pool thread.

Algorithms & Data StructuresSystem DesignTechnical Trade-offs
Author's notes

Chunk boundary math was fine.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the AsyncFuture API and the non-blocking requirement. Then, outline a chunking strategy, concurrent processing using combinators like map and flatMap, and a fold that composes futures without blocking. Finally, discuss trade-offs such as chunk size, error handling, and thread pool utilization.

Pro tip: Emphasize that the fold must be implemented via future combinators (e.g., reduce or foldLeft on futures) rather than awaiting each chunk, to avoid blocking any pool thread. Mention that chunk size should balance parallelism overhead and load balancing.

1. Clarify requirements and API

Ask about the AsyncFuture interface (e.g., map, flatMap, zip) and confirm that no blocking calls (like get or await) are allowed. Also clarify if the input array can be empty and how errors should propagate.

2. Design chunking strategy

Split the array into chunks of roughly equal size, possibly using a fixed chunk size or number of chunks equal to available parallelism. Consider edge cases like empty array or chunk size larger than array.

3. Process chunks concurrently

For each chunk, create an AsyncFuture that processes it (e.g., sums or transforms) on a thread pool. Use combinators to combine these futures without blocking, such as mapping over a list of futures to a future of a list.

4. Fold results non-blockingly

Combine the processed chunk results into a single result using a non-blocking fold operation on futures (e.g., foldLeft with flatMap). Ensure the fold itself does not block any thread.

5. Discuss trade-offs and optimizations

Talk about chunk size tuning, potential for work stealing, error handling (e.g., fail-fast vs. accumulate), and how the solution scales with array size and thread pool capacity.

Key Points to Mention

  • Non-blocking composition using map, flatMap, and other combinators
  • Chunk size selection and its impact on parallelism and overhead
  • Error propagation and handling in asynchronous pipelines
  • Thread pool utilization and avoiding starvation
  • Scalability and performance considerations for large arrays
  • Edge cases: empty array, single element, uneven chunks

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

How would you add cancellation and timeouts to your AsyncFuture, and what should happen to a callback that is already mid-execution when cancellation arrives?

System DesignTechnical Trade-offs
Author's notes

Follow-up question, ran out of time before I could fully answer it.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by outlining the core mechanisms for cancellation and timeouts in an AsyncFuture, such as a cancellation flag and a scheduled timeout task. Then, discuss the semantics of cancellation, especially for callbacks already executing, emphasizing that they should complete but their results should be ignored. Finally, address trade-offs like resource cleanup and thread safety.

Pro tip: Demonstrate awareness of race conditions by mentioning that cancellation and completion can happen concurrently, so you need atomic operations or locks to ensure correctness. Also, note that timeouts are essentially a form of cancellation triggered by a timer.

1. Define cancellation and timeout mechanisms

Explain how to add a cancel() method and a timeout parameter, using a flag or state to track cancellation and a scheduled task to trigger timeout.

2. Handle state transitions atomically

Describe how to atomically transition the future from pending to cancelled or completed, ensuring thread safety and avoiding race conditions.

3. Manage callbacks during cancellation

Specify that callbacks already executing should be allowed to finish, but their results should be discarded, and no new callbacks should be invoked after cancellation.

4. Clean up resources and propagate cancellation

Discuss releasing resources (e.g., threads, timers) and optionally propagating cancellation to upstream tasks if applicable.

5. Discuss trade-offs and edge cases

Address trade-offs like whether to interrupt running callbacks, how to handle multiple cancellations, and the impact on performance and complexity.

Key Points to Mention

  • Atomic state management (e.g., using AtomicBoolean or synchronized blocks) to handle concurrent cancellation and completion.
  • Timeout as a special case of cancellation triggered by a timer (e.g., ScheduledExecutorService).
  • Semantics of cancellation: callbacks already running should complete but results ignored; no new callbacks after cancellation.
  • Resource cleanup: cancelling timers, releasing locks, and avoiding memory leaks.
  • Propagation of cancellation to upstream tasks or dependencies, if the future represents a chain of operations.
  • Trade-offs: whether to interrupt running callbacks (may cause inconsistent state) vs. letting them finish (may waste resources).

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

Your callbacks run on whichever thread completes the future. What are the risks of that design, and how would you offload them to a separate executor?

System DesignTechnical Trade-offs
Author's notes

Answered this one pretty cleanly.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by explaining the risks of executing callbacks on the completing thread, such as thread starvation, priority inversion, and blocking critical threads. Then describe how to offload callbacks to a separate executor, covering design choices like dedicated thread pools, bounded queues, and backpressure. Conclude with trade-offs and best practices for production systems.

Pro tip: Mention that offloading callbacks can introduce latency and ordering issues, so you need to balance responsiveness with throughput and consider using a separate executor with a bounded queue and rejection policy.

1. Identify the risks

Explain how running callbacks on the completing thread can block critical threads (e.g., event loop, I/O threads), cause thread starvation, priority inversion, and make the system vulnerable to slow or malicious callbacks.

2. Describe offloading strategies

Propose using a dedicated executor (e.g., ThreadPoolExecutor) to run callbacks asynchronously. Discuss configuring the executor with appropriate thread pool size, queue type, and rejection policy.

3. Address ordering and latency

Acknowledge that offloading may break callback ordering and add latency. Suggest solutions like per-key serial executors or sequence numbers if ordering matters.

4. Discuss backpressure and resource management

Explain how to handle overload: use bounded queues, backpressure, or rejection policies to prevent resource exhaustion. Mention monitoring and dynamic tuning.

5. Conclude with trade-offs and best practices

Summarize the trade-offs between simplicity and robustness, and recommend best practices like isolating callback execution, using separate executors for different callback types, and testing under load.

Key Points to Mention

  • Thread starvation and blocking critical threads (e.g., event loop, I/O threads)
  • Priority inversion and unfair scheduling
  • Dedicated executor with bounded queue and rejection policy
  • Ordering guarantees and potential need for serial execution per key
  • Backpressure and resource management to avoid overload
  • Latency implications and trade-offs between responsiveness and throughput

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q6

How would you implement an allOf over N futures using a single atomic counter instead of nesting thenCombine calls pairwise, and what does that do to the critical-path depth?

Algorithms & Data StructuresSystem Design
Author's notes

Last question, very last few minutes.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Explain that you would use an atomic counter initialized to N, decremented by each future upon completion, and when it reaches zero, complete a shared promise with the list of results. This avoids pairwise nesting of thenCombine, reducing the critical-path depth from O(N) to O(1) (or O(log N) if using a tree reduction), and also reduces the number of intermediate futures.

Pro tip: Mention that the atomic counter approach is lock-free and scales well, but you must ensure thread-safe collection of results (e.g., using a concurrent data structure or pre-sized array with atomic indices) and handle exceptions properly to avoid deadlocks.

1. Clarify the problem and constraints

Restate the goal: combine N futures into one that completes when all complete, using a single atomic counter instead of nested thenCombine. Discuss assumptions about thread safety, exception handling, and result ordering.

2. Design the atomic counter mechanism

Describe initializing an AtomicInteger to N. Each future, upon completion, decrements the counter. When the counter reaches zero, the last future triggers completion of the combined future.

3. Handle result collection and exceptions

Explain how to safely collect results (e.g., using a pre-sized array with atomic index or a ConcurrentLinkedQueue) and how to propagate exceptions (e.g., if any future fails, complete the combined future exceptionally).

4. Analyze critical-path depth

Compare the depth: pairwise thenCombine creates a chain of N-1 dependent stages, depth O(N). The atomic counter approach has all futures complete independently, and the final completion is triggered by the last one, so depth is O(1) (or O(log N) if using a tree reduction).

5. Discuss trade-offs and alternatives

Mention that the atomic counter approach reduces depth but may increase contention on the counter; alternatives like CompletableFuture.allOf use a similar mechanism internally. Also note that result ordering must be managed explicitly.

Key Points to Mention

  • AtomicInteger as a countdown latch: decrement on each future completion, trigger when zero.
  • Thread-safe result collection: pre-sized array with atomic index or concurrent queue.
  • Exception handling: propagate first exception or aggregate, avoid deadlocks.
  • Critical-path depth: O(N) for pairwise thenCombine vs O(1) for atomic counter (or O(log N) for tree reduction).
  • Contention and scalability: atomic counter may become a bottleneck under high concurrency, but is generally efficient.
  • Comparison with CompletableFuture.allOf: similar approach, but allOf returns CompletableFuture<Void> and requires manual result collection.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.