Google Java Developer Interview Questions & Answers

google icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

21. How would you design aggregate salary queries for a job-posting board?System DesignHardGoogle

Question Details

Design the architecture for a job-posting board that answers aggregate-salary queries across dimensions such as location, job type, category, company, and part-time or full-time status with near-real-time latency.

Short Interview Answer (30-60 seconds)

At a high level, I would treat this as a read-heavy salary aggregation system. The main challenge is returning filtered salary summaries quickly while job postings keep changing. I would split the design into the read path, the posting update path, and the background aggregation path. The API checks a cache first, then reads precomputed aggregates on a miss. Posting changes update those aggregates in the background. The trade-off is faster reads in exchange for more complex writes and results that may be slightly behind.

Detailed Explanation

The goal is to answer salary questions across location, job type, category, company, and employment status. Users want these answers quickly, even while job postings are being created, changed, or expired. Calculating every result from all job postings for every request would be slow. The diagram solves this by keeping ready-made salary summaries for normalized filter combinations. It separates the fast query path from the posting write path and the background work that keeps those summaries updated. This gives near-real-time answers without making every query scan the full job-posting data.

Useful Questions to Ask the Interviewer
  1. Which salary metrics must we return, such as count, average, median, or percentiles?
  2. How fresh do aggregate results need to be after a posting changes?
  3. Which filter combinations are most common and should be precomputed?
How would you design aggregate salary queries for a job-posting board? diagram
How to Explain It in an Interview
1. Explain the goal and the main idea

I would optimize the read path because aggregate salary queries need fast answers. Instead of scanning every job posting during each request, the system keeps precomputed salary aggregates. They are keyed by normalized dimensions such as location, job type, category, company, and employment status. The Materialized Aggregate Store can hold count, average, median, p25, p75, p90, minimum, and maximum salary values. Results may briefly be behind the newest posting changes because aggregate updates happen in the background.

2. Explain the query path

For the read path, the Web / Mobile Client sends an HTTPS aggregate query through the API Gateway. The gateway handles Authentication, Authorization, Request Validation, and Rate Limiting. The Salary Aggregation API runs as Java 21/25 stateless JVM replicas. Each replica is a separate JVM process. Request Handlers parse requests, build normalized query keys, coordinate cache and store access, and serialize JSON responses. They may use virtual threads for concurrent blocking I/O.

Filter Normalization converts location, job type, category, company, and employment status into canonical values. The API then checks the Aggregate Query Cache using the normalized dimensions. On a cache hit, the cached JSON result can be returned quickly. On a cache miss, the request reads the Materialized Aggregate Store, also called the Aggregate Read Model. The Salary Aggregation API returns the JSON response through the API Gateway to the client.

3. Explain the posting write path

For the write path, create, update, or expire requests go to the Job Posting Service. The service saves the posting in the Job Posting DB, which is the source of truth for job postings. It also emits a posting change event to the Posting Change Stream. This lets aggregate maintenance happen separately from the main posting request.

4. Explain the background aggregation work

Aggregation Workers run in Java JVMs and consume events from the Posting Change Stream. They normalize salary data and dimension values. They also deduplicate repeated work and make updates safe when the same event is processed again. They handle both updates and expirations. The workers send incremental aggregate updates to the Materialized Aggregate Store and invalidate affected keys in the Aggregate Query Cache.

If event processing fails, Retry / DLQ handling retries the work with backoff. Backoff means waiting before trying again. Events that still fail can go to the dead-letter queue for later inspection. Observability collects metrics, logs, and traces from the Salary Aggregation API and Aggregation Workers.

5. Explain scale, failures, and trade-offs

The read path scales horizontally because the Salary Aggregation API is stateless across separate JVM replicas. If the Materialized Aggregate Store is temporarily unavailable, the API can serve the last cached result when one exists. The benefit is fast near-real-time reads. The downside is a more complex write path, plus a short delay before the newest posting changes appear in aggregate results.

Engineering Considerations / Design Trade-offs

The benefit is that salary queries are fast because the system reads ready-made aggregate results instead of scanning all job postings. The Aggregate Query Cache makes repeated requests even faster. The downside is that every posting change creates extra background work. Aggregation Workers must update the Materialized Aggregate Store and invalidate affected cache keys. This adds more moving parts and more failure cases. Results can also be a little behind while posting change events are still being processed. We accept this because the design gives much faster reads while still keeping the salary summaries near real time.

Why Interviewers Ask This

Interviewers use this question to see whether you can separate a fast read path from slower background updates. They want to know if you understand caching, precomputed data, event-driven updates, retries, and horizontal scaling. They also want to hear whether you can explain where stale data can appear, how failures are handled, and why the extra write complexity is worth the faster queries.

Interviewer may ask next
What would you change if salary results must reflect every job-posting update almost immediately?

I would keep the same basic design, but I would make the posting update path much more sensitive to delay. The Posting Change Stream and Aggregation Workers would need to process changes quickly so the Materialized Aggregate Store is refreshed soon after each posting update. Cache invalidation should happen immediately after the worker updates the affected aggregate entries.

The Job Posting DB would still remain the source of truth for postings. The Aggregation Workers would still normalize salary data and dimensions, handle repeated events safely, update the read model, and invalidate affected Aggregate Query Cache keys. The read path would stay unchanged, so clients still query through the API Gateway and Salary Aggregation API.

The main downside is higher operational pressure. A backlog in the Posting Change Stream becomes more important because it directly increases result delay. The design still uses background processing, so it cannot promise that every aggregate changes at exactly the same instant as the posting.

What happens if the Materialized Aggregate Store becomes temporarily unavailable?

I would use the fallback already shown in the design. The Salary Aggregation API can serve the last cached result when one exists in the Aggregate Query Cache. This keeps common salary queries working while the Materialized Aggregate Store is temporarily unavailable.

The cache is only a fast read and fallback layer. It does not replace the Materialized Aggregate Store or the Job Posting DB. If a requested normalized key is not present in the cache, the API cannot create a trustworthy aggregate result from nothing. That request may fail until the read model becomes available again.

The Posting Change Stream and Aggregation Workers can continue handling posting changes if their own dependencies remain healthy. Failed event processing still uses Retry / DLQ handling. Observability should expose the problem through metrics, logs, and traces.

The main downside is stale data. Cached results may not include the newest posting changes, so this fallback is useful only as a temporary way to keep some reads available.

22. How would you design a metrics collection system and an image upload pipeline?System DesignHardGoogle

Question Details

Design a metrics collection system for a company and a second system for street-view image taxis that upload images for processing, quality checks, privacy checks, and storage.

Short Interview Answer (30-60 seconds)

At a high level, I would separate fast request handling from background work. The main challenge is handling bursty metrics and large image uploads without making Java services wait for expensive processing. I would explain two flows: the metrics backbone and the street-view image pipeline. Durable queues absorb bursts, while separate JVM workers aggregate metrics and process images. Recent metrics stay quick to query, and only approved images are indexed for internal use. The trade-off is more components and some results arriving later.

Detailed Explanation

The company needs two connected systems. One collects operational metrics from applications and lets dashboards read recent data quickly. The other accepts street-view images, stores originals, checks quality and privacy, and publishes only approved results. Both workloads can arrive in bursts, while aggregation and image processing may take longer than the incoming request. The diagram handles this with shared security controls, stateless Java services, durable queues, separate JVM workers, fast recent storage, and longer-term storage.

Useful Questions to Ask the Interviewer
  1. How bursty are the metrics and image uploads?
  2. How quickly must new metrics appear on dashboards?
  3. How long should raw metrics and original images be kept?
  4. What should happen when an image fails quality or privacy checks?
How would you design a metrics collection system and an image upload pipeline? diagram
How to Explain It in an Interview
1. Start with the shared entry path

I would begin by saying that both workloads pass through the Shared Edge Gateway. It handles authentication, authorization, validation, and rate limiting. Company Services / Applications / Jobs send HTTPS metric writes through it. Street-view Taxis / Camera Units send HTTPS image uploads through the same protected entry point.

2. Explain the metrics backbone

For metrics, the request reaches the Metrics Ingestion Service. It runs as stateless JVM replicas, so it can scale horizontally. The diagram also shows virtual threads inside these JVMs for many blocking requests.

The service places events into the Metrics Event Queue / Durable Log. This queue absorbs bursts and separates producers from consumers. Aggregation / Rollup Consumers run as separate JVM worker replicas. They consume events, roll up metrics, and write recent results to the Recent Metrics Time-Series Store.

The Recent Metrics Time-Series Store also sends data to Raw Metrics Archive Object Storage for long-term retention.

3. Explain the metrics read path

The Query Service serves Dashboards / Alert Consumers. It uses the Cache for hot queries between the query layer and recent metrics storage. Recent requests use the Recent Metrics Time-Series Store. For older history, the Query Service can read Raw Metrics Archive Object Storage on demand.

4. Explain the image upload pipeline

For images, the Upload API / Coordinator accepts the HTTPS upload. It runs as stateless JVM replicas. It writes upload state to the Upload Metadata Store and saves the original image in Raw Image Object Storage before background checks begin.

It also sends work to the Upload Event Queue. Processing Workers consume those events in separate JVM worker replicas. They run Decode / Normalize, Quality Checks, Privacy Checks for faces and license plates, and Final Processing.

Approved results go to Processed Image Storage. Then the Searchable Metadata / Image Index is updated, and Internal Consumers can use the approved images and metadata. Images that fail the checks go to Quarantine / Manual Review.

5. Explain retries, monitoring, and the trade-off

Temporary processing failures follow the retry path through the background workflow. The Upload API / Coordinator, Processing Workers, Query Service, and metrics workers also emit operational metrics into the Metrics Ingestion Service.

The benefit is isolation. Uploads and metric writes do not wait for expensive background work. The downside is more queues, stores, workers, and operational complexity. Some results also appear later because aggregation and image processing happen in the background.

Engineering Considerations / Design Trade-offs

The benefit is that incoming requests stay separate from expensive background work. Durable queues absorb bursts and keep producers from depending directly on workers. Stateless JVM replicas can also scale horizontally. The downside is that queues, workers, caches, archives, and several stores make the system harder to run. Background work also means some results appear later. The Cache for hot queries can make repeated metric reads faster, but it adds another layer to manage. Keeping original images and long-term metric history uses more storage. We accept these costs because the design keeps ingestion responsive and prevents failed image checks from publishing bad data.

Why Interviewers Ask This

The interviewer wants to see whether you can split a large problem into clear flows and choose the right place for background work. They also want to see whether you understand burst handling, Java service scaling, caching, storage, retries, security, and failure isolation. Most importantly, they are testing judgment: can you explain why each part exists, how the parts connect, and what trade-offs you accept.

Interviewer may ask next
What would you change if street-view taxis suddenly uploaded much larger bursts of images?

I would keep the same basic architecture and scale the parts that absorb and process the burst. The Shared Edge Gateway would still enforce authentication, validation, and rate limiting. The Upload API / Coordinator would still save the original image and upload state before sending work to the Upload Event Queue.

The main change would be capacity. I would add more Upload API / Coordinator JVM replicas when request traffic grows. I would also add more Processing Worker replicas when the queue backlog grows. Because the queue already separates uploads from processing, taxis do not wait for Decode / Normalize, Quality Checks, Privacy Checks, or Final Processing to finish.

Correctness stays the same. Approved images still go to Processed Image Storage and the Searchable Metadata / Image Index. Failed images still go to Quarantine / Manual Review.

The downside is higher cost. A very large backlog can also make approved images take longer to appear for Internal Consumers.

What happens if the Recent Metrics Time-Series Store cannot serve a query?

I would keep the same Query Service and storage layout. The normal path still uses the Cache for hot queries and the Recent Metrics Time-Series Store for recent data. The diagram also lets the Query Service read Raw Metrics Archive Object Storage for older history.

If the recent store cannot serve a request, a hot query may still be answered from the cache when the needed result is already there. Older history can still be read from the archive on demand. I would not claim that the archive fully replaces the recent store, because the diagram does not show that guarantee. Some recent dashboard data may therefore be missing or delayed until the recent store recovers.

Metrics ingestion can continue through the Metrics Event Queue / Durable Log, so a read-side problem does not automatically stop producers.

The downside is reduced freshness. Historical data may remain available, but the newest dashboard view may be incomplete during the failure.

23. How would you handle Gmail usernames claimed by two users at the same time?System DesignMediumGoogle

Question Details

Design how you would handle Gmail accounts when two users from different countries create the same username at the same time, including consistency and conflict handling.

Short Interview Answer (30-60 seconds)

At a high level, this is a correctness-first signup flow. Two users may reach different Java services in different countries, but only one should win the same username. I would break it into three parts: protect and validate the request, reserve the username with a globally shared lock and a unique database write, and then return success or conflict while background jobs handle email, search, and monitoring. The trade-off is a little extra latency, but we avoid duplicate ownership.

Detailed Explanation

This question asks how Gmail should handle a race for the same username. Two people may press sign up at almost the same time, and they may be far apart in the world. The system still must choose only one winner. The diagram shows a simple plan. First, it checks and cleans the request. Then it reserves the name once. Then it saves the account and sends back success or conflict. Extra work like email, search, and monitoring runs later in the background.

Useful Questions to Ask the Interviewer
  1. If two users race for the same name, should the loser get a retry hint or a clear conflict right away?
  2. Do we need one global username namespace across all countries, or any country-specific rules?
  3. Is a short Retry-After response acceptable when the reservation is already taken?
How would you handle Gmail usernames claimed by two users at the same time? diagram
How to Explain It in an Interview
1. Explain the goal and the main idea

At a high level, the goal is simple. One username should have one owner, even when two requests arrive together. The tricky part is the race. Two users can hit different replicas at the same time, so a quick local check is not enough.

The diagram uses a simple rule. First protect the request at the edge. Then let a Java service try to reserve the username. Then write the final account data in the SQL database. That gives us one clear winner and one clear loser.

2. Explain the request path

For the create path, the request first goes through the global load balancer and the edge gateway. The edge layer does authentication, abuse checks, input validation, normalization, and rate limiting. Normalization means we turn the name into one standard form, such as lowercase and NFC Unicode, so "Same" and "same" do not act like different names.

This part matters because we want to reject bad traffic early. It also keeps the main Java service focused on the real signup work.

3. Explain how the race is resolved

The account service is stateless, so many Java 21 replicas can run at the same time. It first checks availability, but that check is only a quick hint. The real decision comes from the username reservation step. The service tries to acquire a shared lock for the normalized username. Only one request can hold it.

The lock has a TTL, which means time to live. In simple words, the lock expires if something goes wrong and nobody releases it. If the lock is already held, the service can return conflict or Retry-After. If the lock is acquired, the service writes the account and the username index. The unique username rule in the database is the final safety net.

4. Explain background work and responses

Once the write succeeds, the service maps the result and returns 201 Created. After that, it publishes events for the welcome email service, the search indexer, and analytics and monitoring. Those jobs run in the background, so they do not slow the signup response.

The diagram also shows audit logs and observability. Every request carries a request_id, and the system records logs, traces, dashboards, and compliance data. That makes it much easier to debug a race or a bot attack later.

5. Explain scale, failures, and trade-offs

This design scales because the Java service is stateless. We can add more replicas without changing the core rule. The lock service and the SQL unique constraint keep the username correct across countries. The downside is that this is not the cheapest or fastest path. Strong consistency and lock coordination add a little delay.

That trade-off is worth it here. A username must never end up with two owners. So the system prefers a slightly slower signup over a wrong signup.

Engineering Considerations / Design Trade-offs

The benefit is strong correctness. Only one request can reserve the username, and the SQL unique rule protects the final write. The Java service can still scale because it is stateless, and the background email, search, and monitoring work do not slow the main response. The downside is extra coordination. The lock step and the database check add a little latency, and one of the two users may get a conflict or retry message. That is a fair trade-off because username ownership must stay correct.

Why Interviewers Ask This

Interviewers want to see if you can keep a system correct when two requests collide. They also want to see whether you choose a clear source of truth, use the cache or lock layer the right way, and separate the fast signup path from the background work. This question checks judgment, not memorization.

Interviewer may ask next
What would you do if the shared reservation service becomes slow in one region?

I would keep the same basic design, but I would fail safely. If the Java service cannot get the reservation, it should not guess. It should return a retryable conflict or send the request to another healthy region that uses the same shared reservation system. The SQL unique rule still protects the final write, so even retries cannot create two owners.

I would also keep the lock TTL short enough to recover from stuck requests, but long enough to cover a normal signup. That keeps the system from blocking forever when something goes wrong. The downside is that users may see a slower signup or a short retry message during an incident. That is still better than letting two users claim the same username.

How would you make the user experience friendlier when the username is taken?

I would keep the same core race handling, but I would improve the response. After the reservation check fails, the service can return a clear conflict message and suggest close alternatives, such as a small variation of the name. The important thing is that suggestions do not change the final rule. The lock and the database still decide the real winner.

I would also make the conflict response simple and fast. That way the user gets an answer right away instead of waiting for background work that does not matter yet. If the service already knows the name is taken, it should say that clearly and let the user try a new name. The downside is extra product logic in the response path, but the correctness model stays the same.

24. How would you merge two companies' photo-sharing platforms?System DesignMediumGoogle

Question Details

Imagine you are in charge of merging two companies' data and technology platforms for photo sharing; explain how you would approach the system design and migration.

Short Interview Answer (30-60 seconds)

At a high level, I would merge the two photo-sharing platforms gradually instead of doing one risky cutover. The main challenge is keeping identities, permissions, metadata, and photo files correct while both legacy systems still serve users. I would explain three flows: normal user requests, account merging, and background migration. The Unified Photo Platform becomes the final system, while workers copy and verify legacy data in stages. The trade-off is temporary migration complexity in exchange for safer rollback and less disruption.

Detailed Explanation

The goal is to combine two working photo-sharing platforms into one system without losing accounts, permissions, albums, metadata, or photo files. The hard part is that users may still depend on both legacy platforms while their data is moving. We therefore need a stable path for normal traffic and a separate path for migration work. The diagram solves this with a Unified Photo Platform, identity linking, staged data migration, background workers, integrity checks, and a rollback path that remains available until the final cutover is validated.

Useful Questions to Ask the Interviewer
  1. Must both legacy platforms remain online throughout the migration?
  2. Can one person have accounts on both platforms that need linking?
  3. How much downtime, if any, is acceptable during final cutover?
  4. Must all historical albums, shares, permissions, and original photos be preserved?
How would you merge two companies' photo-sharing platforms? diagram
How to Explain It in an Interview
1. Start with the normal request path

For normal traffic, Web App and Mobile Apps enter through the CDN / Edge. Requests then reach the API Gateway, which routes traffic, validates input, applies rate limits, and performs authentication and authorization checks. The Identity Merge / Auth Service accepts login from Platform A or Platform B, links identities, and issues a unified user identity and token. The request then enters the Unified Photo Platform, where the online Java 21/25 services run as replicated JVMs.

2. Keep account, photo, and social responsibilities clear

The Account Merge Service handles identity linking, account ownership, authorization, profile merging, and auditing. The Photo API Service handles photo upload and download, photo metadata, permissions, and media URLs. The Album / Feed Service handles albums, feeds, sharing, and search-related reads. These services use the Unified User Store, Unified Photo Metadata DB, canonical Object Storage, Search Index, and Cache. The Cache is only a faster lookup layer. The unified stores remain the final data destination.

3. Migrate legacy data in controlled stages

The Admin / Migration Ops Console controls the Migration Orchestrator. The orchestrator reads from Legacy Platform A and Legacy Platform B through connectors and tracks progress with checkpoints. The migration follows six stages shown in the diagram: inventory and schema mapping, user identity mapping and linking, metadata transformation and deduplication, photo copy and checksum verification, incremental backfill with delta sync, and final cutover with validation.

4. Run heavy migration work in the background

The Migration Orchestrator publishes migration tasks to the Event Bus / Queue. Background Consumer Workers run in separate worker JVMs and consume those tasks. They transform and write data, process indexes, and perform work that should not block normal user requests. Failed events can move to the Dead-Letter Queue. Retries use backoff, which means the system waits longer between repeated attempts. Migration jobs are idempotent, meaning repeating the same job should not create duplicate results.

5. Make cutover safe and reversible

During migration, the system can use dual reads and route a read back to a legacy platform when needed. Changed metadata may use limited dual writes during the transition. Photo checksums verify that copied media still matches the source. Logs, metrics, traces, and alerts show migration and cutover health. If validation fails, traffic can return to the legacy read path until the problem is fixed. Virtual threads can help the Java services handle many blocking I/O operations, but they do not replace the queue, backpressure, retries, or resource limits.

Engineering Considerations / Design Trade-offs

The benefit is that the migration happens in small, controlled steps instead of one dangerous move. Users can keep using the service while workers copy accounts, metadata, and photos. Checkpoints and jobs that are safe to repeat make recovery easier. Checksums help verify photo integrity. The downside is temporary complexity. For some time, the team must operate the new platform, both legacy platforms, connectors, queues, workers, fallback reads, and monitoring together. Dual reads and limited dual writes also need careful testing. We accept this extra work because it gives us a safer cutover, better visibility, and a clear rollback path.

Why Interviewers Ask This

Interviewers ask this to see whether you can break a large migration into safe steps. They want to know how you protect identity, ownership, permissions, and photo data while systems are still live. They also test your judgment around background processing, retries, verification, rollback, security, and operational risk. The key skill is showing how two running platforms can become one without treating migration as a simple database copy.

Interviewer may ask next
What would you change if both legacy platforms had to remain fully available during the entire migration?

I would keep the same design, but I would depend more on the phased migration and fallback paths already shown in the diagram. Normal traffic would continue through the Unified Photo Platform while the Migration Orchestrator moves data in small batches.

If requested information has not reached the unified stores yet, the system can use a dual-read path and read from the appropriate legacy platform. The incremental backfill stage would continue moving changes made after the first copy. If changed metadata must temporarily exist in both places, I would use the limited dual-write option shown in the operational notes.

Correctness would come from checkpoints, idempotent migration jobs, and checksum verification for copied photo files. Observability would show errors and cutover health. I would not retire either legacy platform until final validation succeeds.

The downside is a longer transition. More paths remain active, so testing, monitoring, and operational work become harder.

How would you handle a Background Consumer Worker crashing halfway through a photo migration task?

I would keep the same Event Bus / Queue and worker design and make the failed task safe to retry. The Migration Orchestrator tracks progress with checkpoints, so one worker crash should not force the whole migration to restart.

When another Background Consumer Worker receives the task again, it can continue the work safely. The migration job is idempotent, which means running the same task again should not create duplicate account, metadata, or photo results. For copied photos, the worker can use the checksum verification stage before considering the copy complete.

If a task repeatedly fails, it can move to the Dead-Letter Queue. That keeps broken work from blocking healthy migration events. Logs, metrics, traces, and alerts help operators understand the failure.

The downside is extra implementation work. Checkpoints, duplicate protection, retries, and failed-event handling add complexity, but they make a long-running migration much safer.

25. How would you design an API for versioned JSON diff storage?API DesignMediumGoogle

Question Details

Design an API for a backend system that stores all versions of a JSON object and reduces storage by storing diffs between versions.

Short Interview Answer (30-60 seconds)

At a high level, I would keep every JSON version while storing only the changes between most versions. A client writes with PUT /objects/{id}, sending new JSON, a JWT, and If-Match. The API authenticates the caller, verifies the expected version, rebuilds the current JSON, validates the new JSON, and creates an RFC 6902 JSON Patch. Diff Store keeps the patch, Metadata DB tracks versions and snapshot references, and Snapshot Store keeps full checkpoints. Reads use GET /objects/{id}?version=vK and reconstruct the requested version. The trade-off is lower storage use for extra reconstruction work.

Detailed Explanation

This question asks us to save every version of a JSON object without storing a complete copy for every change. We must still be able to read an older version later. The main challenge is balancing storage space with the work needed to rebuild old data. The diagram solves this by keeping full JSON snapshots at selected versions and patches between versions. It also protects writes with an expected-version check, so an older client cannot silently overwrite a newer update. I would explain the write flow first, then the historical read flow.

Useful Questions to Ask the Interviewer
  • How often are historical versions read compared with new versions being written?
  • How large can each JSON object become?
  • How frequently should the system keep a full JSON snapshot?
How would you design an API for versioned JSON diff storage? diagram
How to Explain It in an Interview
1. Define the API boundary and operations

I would expose one write operation and one historical read operation. The write path is PUT /objects/{id}. The client sends the new JSON together with a JWT and an If-Match value. The read path is GET /objects/{id}?version=vK. That query asks for one specific historical version. Both requests enter the Java Versioned JSON API through the REST Controller. The controller passes the work to the components responsible for authentication, version handling, reconstruction, validation, and diff creation.

2. Authenticate the caller and protect concurrent writes

For a write, the REST Controller sends the request to Auth + Concurrency Check. The JWT identifies the caller. The If-Match value represents the version the client expects to update. The diagram shows this component authenticating the caller and verifying the expected version before allowing an authorized write to Version Service. The ETag and If-Match rule prevents an older client from overwriting a newer version. If the expected version is no longer current, the update must not continue. The diagram does not assign a specific HTTP error status to that rejection, so I would not invent one.

3. Rebuild the current JSON before calculating the change

Version Service asks Reconstruction Service to load the current version. Reconstruction Service gets version metadata from Metadata DB. The database keeps the object id, latest version, and snapshot references. Reconstruction Service also finds the nearest full JSON in Snapshot Store and reads the required patches from Diff Store. Snapshot Store contains full JSON for version 1 and every Nth version. Diff Store contains version-to-version patches. Reconstruction Service rebuilds the current document and returns current JSON to Version Service.

4. Validate the new document and create the diff

Version Service sends the incoming document to JSON Validator + Canonicalizer. This component validates and normalizes the JSON. It returns canonical JSON to Version Service. Version Service then asks Diff Engine to compute the change from the old JSON to the new JSON. Diff Engine produces a JSON Patch using RFC 6902. The patch describes operations such as adding, removing, or replacing JSON values instead of storing another complete document.

5. Store the new version information

The diagram shows the generated patch being persisted in Diff Store as the patch for the next version. Metadata DB receives the persistence flow labeled increment version + update refs, so it tracks the latest version and snapshot references. Snapshot Store holds full JSON checkpoints for version 1 and every Nth version. Those periodic checkpoints are the storage optimization shown in the diagram. They reduce the number of patches that Reconstruction Service must replay later. The design also writes an audit event to Audit / Access Logs. After a successful write, the response returns through the REST Controller to the API Client as 201 Created with the version and ETag.

6. Reconstruct an exact historical version

For GET /objects/{id}?version=vK, the REST Controller sends the requested version into the versioning flow. Version Service asks Reconstruction Service to rebuild the target version. Reconstruction Service reads metadata, finds the nearest snapshot, and fetches the patches needed up to vK. It applies those patches in version order to reconstruct the requested JSON. The result returns through Version Service and the REST Controller. The client receives 200 OK with the JSON and version metadata.

7. Explain the main trade-off

The main benefit is storage efficiency. Most versions need only a patch instead of another full JSON document. The downside is extra read work because historical data may need reconstruction. Periodic snapshots bound that replay cost. A smaller snapshot interval uses more storage but makes reconstruction faster. A larger interval saves more storage but may require applying more patches.

Practical Complexity & Trade-offs

The benefit is lower storage use because most versions keep only an RFC 6902 JSON Patch. Full snapshots stop the patch chain from becoming too long. The downside is that reading an older version can require loading one snapshot and applying several patches. A smaller checkpoint interval improves read speed but stores more full JSON copies. A larger interval saves more space but increases reconstruction work. JWT authentication checks who is calling. If-Match with ETag protects against stale writes replacing newer data. Metadata, snapshots, patches, and audit events also need to remain consistent with the version being created. The diagram keeps audit logging outside the business response path, so logging records activity without owning the API result.

Why Interviewers Ask This

Interviewers use this question to test API modeling and engineering judgment. They want to see whether you can keep historical data while controlling storage growth. They also look for correct request and response directions, safe concurrent writes, clear ownership between versioning, reconstruction, validation, and storage components, and a practical reconstruction strategy. A strong answer explains the snapshot-versus-diff trade-off clearly instead of only naming technologies.

Interviewer may ask next
What would you change if an object has thousands of versions and historical reads become slow?

I would keep the same API and reconstruction model, but create full snapshots more frequently. The affected path is the read flow through Version Service and Reconstruction Service. Reconstruction Service would still read Metadata DB, find the nearest entry in Snapshot Store, and fetch patches from Diff Store, but it would usually apply fewer patches before reaching the requested version. The write-side storage policy would therefore use a smaller value for N when deciding how often a full JSON checkpoint exists. Correctness remains the same because a checkpoint represents an exact object version, and the following patches still describe ordered version-to-version changes. JWT authentication and the If-Match/ETag concurrency protection do not change. The main downside is higher storage consumption because more versions are kept as complete JSON documents. I would choose the checkpoint interval from measured read latency, object size, patch size, and how often users request old versions.

How does this design prevent two clients from silently overwriting each other's updates?

I would use the existing If-Match and ETag concurrency flow shown in the diagram. Each client sends PUT /objects/{id} with the version it believes is current. The REST Controller sends the request to Auth + Concurrency Check, which authenticates the JWT and verifies the expected version. Only an authorized write with the expected current version continues to Version Service. That flow rebuilds the current JSON, validates and canonicalizes the new JSON, computes the RFC 6902 patch, and persists the resulting version information. The successful response returns 201 Created with the new version and ETag. If another client still has an older expected version, the concurrency check prevents that stale write from overwriting the newer state. The diagram does not specify the exact HTTP error code for this failure, so I would not invent one. The downside is that the stale client must obtain the newer state before trying its change again.

26. How would you design a restaurant waitlist API?API DesignMediumGoogle

Question Details

Design a waitlist API for a restaurant where parties can join, leave, and be seated by table size and arrival order.

Short Interview Answer (30-60 seconds)

At a high level, I would design the waitlist around four actions: join, leave, view the waiting queue, and seat a party. Customers use POST /waitlist and DELETE /waitlist/{partyId}. The host uses GET /waitlist?status=WAITING and POST /tables/{tableId}/seat. For seating, the API chooses the earliest WAITING party whose size fits the selected table. The key reliability decision is one atomic database transaction for seating changes. Notifications happen asynchronously. The trade-off is simplicity, while the database carries most consistency work.

Detailed Explanation

This problem is about helping a restaurant manage people waiting for tables. A party should be able to join the list or leave it. The host should be able to see who is still waiting. When a table becomes available, the system should choose the earliest waiting party that can fit at that table. The main challenge is keeping arrival order, party state, and table state correct together. I would explain the solution in the same order as the diagram, from the client requests through the Java Waitlist API and Restaurant DB.

Useful Questions to Ask the Interviewer
  • Are we designing this for one restaurant location or multiple locations?
  • Should arrival order remain strict among all parties that fit the selected table?
  • Does the host choose the table first, as this design assumes?
  • Is it acceptable for SMS or push notification to happen after seating succeeds?
How would you design a restaurant waitlist API? diagram
How to Explain It in an Interview
1. Define the main API boundary

I would place one Java Waitlist API between the clients and the Restaurant DB. The Customer App / Web uses it to join and leave the waitlist. The Host Stand App uses it to view the queue and seat a party.

The database keeps three kinds of information shown in the diagram. WaitlistParty stores the waiting party. RestaurantTable stores table information. SeatingHistory records completed seating actions.

2. Let a customer join or leave

To join, the Customer App / Web sends HTTPS JSON to POST /waitlist. The request contains name, phone, and partySize.

The Join / Leave Endpoints create a WAITING party in the Restaurant DB. The stored data includes status, joinedAt, and quotedWait. The database returns the saved partyId. The endpoint then returns 201 Created with partyId, status=WAITING, and quotedWait.

To leave, the customer sends DELETE /waitlist/{partyId}. The Join / Leave Endpoints update that party to status=LEFT in the Restaurant DB. The API then returns 200 OK to the customer.

3. Let the host view the ordered queue

The Host Stand App sends HTTPS JSON to GET /waitlist?status=WAITING. The Queue Query reads the waiting parties from the Restaurant DB.

The query orders them by joinedAt ascending. In simple terms, earlier arrivals appear before later arrivals. The database sends the ordered queue back to the Queue Query. The API then returns 200 OK with the ordered waitlist to the Host Stand App.

4. Seat the earliest party that fits

The host sends HTTPS JSON to POST /tables/{tableId}/seat. The Seating Engine handles this seating request.

It applies the Seating Rule shown in the diagram. The rule is to pick the earliest WAITING party whose partySize is less than or equal to the selected table capacity.

The seating flow reads the table capacity and selects the first matching WAITING party ordered by joinedAt ascending. The Restaurant DB returns the matching party and table information.

5. Update seating state atomically

The most important reliability decision is the atomic transaction. Atomic means all related database changes succeed together or do not become a partial result.

The transaction changes the party to SEATED, changes the RestaurantTable to OCCUPIED, and inserts a SeatingHistory record. The Restaurant DB then returns commit success.

This prevents an inconsistent result such as a party becoming SEATED while the same table still appears available. After the successful commit, the seating flow returns 200 OK to the Host Stand App with seatedPartyId and tableId.

6. Send the table-ready notification asynchronously

After successful seating, the seating flow sends an async party seated event to the Notifier. Async means this work does not need to finish before the main seating response completes.

The Notifier sends an SMS or push message to the Customer App / Web saying the table is ready. This keeps notification work outside the main synchronous seating path. The benefit is that notification delivery does not need to delay the host's successful seating response. The downside is that the customer message may arrive shortly after the database commit.

Practical Complexity & Trade-offs

The design stays simple because one Java Waitlist API handles the main actions and one Restaurant DB keeps the important state. The benefit is clear consistency. The seating rule is easy to understand: choose the oldest WAITING party that fits the selected table. The atomic transaction is the main reliability choice because party state, table state, and SeatingHistory change together. The downside is that the database becomes central to both reads and writes. The async Notifier keeps SMS or push work outside the main seating response. This keeps the host flow fast, but notification can arrive later. The diagram does not define authentication, pagination, rate limiting, retries, caching, or extra error codes, so I would not add those behaviors to this design.

Why Interviewers Ask This

This question tests whether a candidate can turn a simple restaurant rule into a clear API design. The interviewer wants to see correct HTTP methods, request and response direction, sensible resource boundaries, and correct arrival-order logic. The seating operation also tests consistency judgment because several pieces of state change together. A strong answer should explain why one transaction matters and why notification can happen asynchronously. The interviewer is mainly testing engineering judgment and clear communication.

Interviewer may ask next
What would you change if several hosts try to seat parties at the same time?

I would keep the same endpoints and seating rule, but I would strengthen the database transaction around the seating decision. The affected flow is POST /tables/{tableId}/seat through the Seating Engine and Restaurant DB. The rule still selects the earliest WAITING party whose partySize fits the table capacity.

The important requirement is preventing two successful requests from seating the same party or occupying the same table. The existing diagram already makes the database transaction the consistency boundary. Under higher concurrency, the selection and the related party, table, and SeatingHistory changes should remain protected inside that transaction so conflicting changes cannot both commit successfully.

The customer join, leave, and queue-reading flows do not need to change. A successful seating request still returns 200 OK with seatedPartyId and tableId after commit success. The downside is more database coordination when many hosts act at once. That can reduce throughput around heavily contested tables or parties.

What happens if the SMS or push notification is slow after seating succeeds?

I would keep the seating result independent from notification delivery. The affected flow begins after the Restaurant DB successfully commits the seating transaction. At that point, the party is already SEATED, the RestaurantTable is OCCUPIED, and the SeatingHistory record has been inserted.

The seating flow can return 200 OK with seatedPartyId and tableId to the Host Stand App. Separately, the async party seated event goes to the Notifier. The Notifier then sends the SMS or push message to the Customer App / Web.

Because the notification path is asynchronous in the diagram, a slow notification should not delay the host's successful seating response. The core restaurant state remains correct before the customer message is delivered. The downside is timing: the customer may receive the table-ready message shortly after the host completes seating. The diagram does not show retry, fallback, or delivery guarantees, so I would not claim any specific retry behavior.

27. How would you design allocate and deallocate operations for host tracking?API DesignMediumGoogle

Question Details

Implement a Tracker utility with allocate(hostType) and deallocate(hostType, index), returning the first available instance index per host type.

Short Interview Answer (30-60 seconds)

At a high level, I would keep one HostPool for each host type. Each pool tracks the next unused index, the smallest freed indices, and the indices currently allocated. allocate(hostType) reuses the smallest freed index first; otherwise, it returns nextIndex and increments it. deallocate(hostType, index) validates that the index is active, removes it, and returns it to the free-index min-heap. This keeps allocation correct and efficient. For concurrent access, I would protect each HostPool separately. The trade-off is extra state, heap work, and synchronization.

Detailed Explanation

The problem is to give each host type its own sequence of small instance numbers. When a new host needs a number, we should return the smallest number that is currently available. When a host is removed, its number should become available again. The main challenge is avoiding duplicate active numbers while still reusing released numbers in the correct order. The diagram solves this with one HostPool for each host type. Both allocate and deallocate use the same HostPool state, so the tracker can make each decision consistently.

Useful Questions to Ask the Interviewer
  • Should each host type have its own independent index sequence?
  • Should released indices always be reused before issuing a new index?
  • Can multiple threads call allocate and deallocate at the same time?
  • Should an invalid deallocation return false or raise an error?
How would you design allocate and deallocate operations for host tracking? diagram
How to Explain It in an Interview
1. Define the Tracker API and its state

I would expose two operations. allocate(hostType): int returns an instance index. deallocate(hostType, index): boolean releases an existing index. The Provisioning Service or Caller sends each request to the Java Tracker Utility, and the utility returns the result to that caller.

The tracker owns Map<String, HostPool> poolsByType. The key is the host type, such as web or db. The value is that host type's HostPool. This means every host type has an independent allocation sequence.

Each HostPool contains nextIndex, freeIndices, and allocated. nextIndex stores the next index that has never been issued. freeIndices is a PriorityQueue<Integer> used as a min-heap, so its smallest value is removed first. allocated is a HashSet<Integer> containing the indices that are currently active.

2. Handle allocate(hostType)

When the caller sends allocate(hostType), the tracker first looks up or creates that host type's HostPool.

It then checks whether freeIndices is empty. If it is not empty, the tracker calls freeIndices.poll(). This returns the smallest previously freed index.

If freeIndices is empty, the tracker uses nextIndex as the result and then increments nextIndex for the next new allocation.

Both branches then call allocated.add(index). The Java Tracker Utility returns that index to the Provisioning Service or Caller as the allocated-index response.

3. Handle deallocate(hostType, index)

When the caller sends deallocate(hostType, index), the tracker first looks up the HostPool for that host type.

The tracker validates allocated.contains(index). This check prevents an index that is already free from being added to the free-index heap again.

If the index is valid, the tracker calls allocated.remove(index) and then freeIndices.offer(index). The released index is now available for a later allocation. The tracker returns true to the caller.

If the host type is invalid or the index is not currently allocated, the tracker returns false or an error. It does not add anything to freeIndices. This prevents duplicate freed indices.

4. Preserve the first-available-index rule

The key requirement is to return the first available instance index for each host type. The min-heap gives us that behavior because poll() always removes the smallest freed index.

For example, suppose web receives indices 0, 1, and 2. If index 1 is deallocated, the heap contains 1. The next allocate("web") call therefore returns 1 instead of issuing 3.

The allocated set serves a different purpose. It tracks which indices are active and makes deallocation validation simple.

5. Explain performance and concurrency

If no freed index exists, allocating with nextIndex is O(1). Reusing a freed index costs O(log f), where f is the number of free indices. Deallocation also costs O(log f) because inserting the released index into the min-heap costs O(log f).

If multiple threads can access the tracker concurrently, I would guard each HostPool with a per-host-type lock or synchronized section. The check-and-update steps must happen together. This prevents two threads from allocating or freeing the same index inconsistently. A per-host-type lock also allows operations for different host types to continue independently.

Practical Complexity & Trade-offs

The benefit of this design is that it directly preserves the first-available-index rule. The min-heap always gives the smallest released index, while the HashSet tells us whether an index is currently active. When no released index exists, allocate can use nextIndex in O(1). Reusing a released index costs O(log f), where f is the number of free indices. Deallocate also costs O(log f) because the released index is inserted into the heap. The downside is extra memory because each HostPool keeps several pieces of state. Concurrent access adds another cost. A per-host-type lock keeps each HostPool consistent, but calls for the same host type may wait for each other. We accept this because it keeps the design simple and correct while allowing different host types to proceed independently.

Why Interviewers Ask This

This question tests whether you can turn a small stateful requirement into a clean API design. The interviewer wants to see whether you separate state by host type, choose data structures that preserve the smallest-available-index rule, validate deallocation correctly, and model request and response flow clearly. It also tests whether you notice concurrency risks and can discuss performance trade-offs without adding unnecessary infrastructure. The important signal is practical engineering judgment and clear reasoning.

Interviewer may ask next
How would the design change if many threads call allocate and deallocate at the same time?

I would keep the same API and the same HostPool data structures, but I would protect each HostPool with a per-host-type lock or synchronized section. For allocate(hostType), looking at freeIndices, choosing either freeIndices.poll() or nextIndex, updating nextIndex when needed, and calling allocated.add(index) must behave as one protected operation. Otherwise, two threads could observe the same state and return the same index. For deallocate(hostType, index), checking allocated.contains(index), removing the index, and adding it to freeIndices must also happen together. That prevents duplicate frees and inconsistent state. The request and response contracts remain unchanged. allocate still returns an index, and deallocate still returns true or false or an error on the invalid path. I would prefer one lock per HostPool instead of one global lock. The downside is contention when many threads use the same host type, but different host types can still progress independently.

Why does each HostPool need both a PriorityQueue and a HashSet?

I would keep both because they solve different problems. freeIndices, implemented as a PriorityQueue<Integer> min-heap, tells allocate which released index should be reused first. Calling poll() returns the smallest freed index, so the first-available-index rule is preserved. The allocated HashSet tracks which indices are currently active. Before deallocation, the tracker checks allocated.contains(index). If the index is active, it removes it from the set and offers it to the min-heap. If the index is not active, the tracker returns false or an error and does not change the heap. Without the HashSet, a duplicate deallocation could place the same index into freeIndices more than once. Without the PriorityQueue, the tracker would not efficiently know which freed index is smallest. The downside is extra memory and maintaining two structures, but each has a clear responsibility and together they match the required behavior.

28. How would you design an API that returns test cases by assigned percentage?API DesignMediumGoogle

Question Details

Create an API interface for a testing team that returns API test cases based on percentage assignments such as 50%, 20%, 20%, and 10%.

Short Interview Answer (30-60 seconds)

At a high level, I would expose one POST API that returns a requested number of test cases using percentage assignments. The testing team sends POST /v1/test-case-selections over HTTPS with a JWT, a total count, and category percentages. The service validates the request, calculates target counts, loads active candidate cases, removes duplicates, and selects the final cases. It returns 200 OK with the allocation summary and selected test cases. Invalid percentages return 400 Bad Request. A category shortage returns 409 unless borrowing is allowed. The main trade-off is strict percentage accuracy versus completing more requests.

Detailed Explanation

This question asks us to build a simple way for a testing team to request a mixed set of test cases. For example, the team may want 100 cases split into 50 smoke, 20 regression, 20 integration, and 10 security tests. The main challenge is making sure the percentages create the requested total and that enough usable test cases exist. The diagram solves this with one Java Spring Boot API Service. It validates the request, calculates category counts, reads candidate cases, selects the final set, returns the result, and records useful operational information.

Useful Questions to Ask the Interviewer
  • Should the percentages always add up to exactly 100?
  • What should happen when one category has too few test cases?
  • Should allowBorrow=true let another category fill a shortage?
  • Can the same test case belong to more than one requested category?
How would you design an API that returns test cases by assigned percentage? diagram
How to Explain It in an Interview
1. Start with the API contract

I would expose POST /v1/test-case-selections. The Testing Team / CI Job sends the request over HTTPS with a JWT. The diagram's example request contains suiteId: checkout-api, totalCount: 100, and assignments of smoke 50, regression 20, integration 20, and security 10. The REST Controller receives and parses this request inside the Java / Spring Boot API Service. The diagram shows this service as a single JVM application. The REST Controller then sends the parsed request to the Request Validator.

2. Validate the request before doing selection work

The Request Validator checks the rules shown in the diagram. The percentages must add up to 100. Categories must be unique. totalCount must be greater than zero. These checks prevent invalid input from reaching the allocation logic. The visible failure path returns 400 Bad Request when the percentages do not equal 100. After validation succeeds, the request moves to the Percentage Allocation Service, which owns the percentage-to-count calculation.

3. Calculate the target count for each category

The Percentage Allocation Service converts each percentage into a target number of test cases. The diagram uses floor(total × % / 100) as the starting calculation. If flooring leaves a remainder, the service distributes that remainder so the requested total can still be reached. With a total of 100 and percentages of 50, 20, 20, and 10, the target counts are 50, 20, 20, and 10. The service then asks the Repository for active candidate test cases by category.

4. Read candidate cases through the Repository

The Repository owns access to the Test Case Store. The store keeps test-case data such as id, name, api, category, and enabled. The Repository obtains candidate cases and returns them to the Percentage Allocation Service as candidate cases per bucket. Keeping storage access in the Repository separates data access from allocation and selection rules. The Percentage Allocation Service can therefore focus on calculating counts while the Repository handles reading the stored test cases.

5. Select the final test cases

The Percentage Allocation Service passes the target counts into the Test Case Selector. The selector follows the visible selection rules. It picks enabled cases only and deduplicates cases across categories. If a category is short, the diagram says to return 409 unless allowBorrow=true. This creates an important design decision. Strict mode keeps the requested category allocation intact, but it can reject requests when there are not enough cases. Allowing borrowing can make more requests succeed, but the final category mix may differ from the original percentages.

6. Return the result and record operational data

After selection succeeds, the Test Case Selector sends the selected cases and allocation summary back to the REST Controller. The REST Controller returns 200 OK JSON to the Testing Team / CI Job. The diagram's response example contains requestedTotal: 100, allocated: 50/20/20/10, and testCases: [...]. The Test Case Selector also sends the request ID, counts, and latency to Audit Logs / Metrics. This logging flow is separate from the business response. It helps the team understand usage and performance without changing the returned test-case result.

Practical Complexity & Trade-offs

The benefit of this design is that each part has one clear job. The Request Validator rejects bad input early. The Percentage Allocation Service calculates how many cases each category needs. The Repository handles access to the Test Case Store. The Test Case Selector applies enabled-only and deduplication rules. HTTPS protects the request while it travels, and the request carries the JWT shown in the diagram. The main trade-off is shortage handling. Returning 409 keeps the requested percentage mix strict, but some requests may fail. Allowing borrowing can make more requests succeed, but the final mix can change. Remainder distribution also needs a consistent rule so the final counts reach the requested total.

Why Interviewers Ask This

Interviewers use this question to test whether a candidate can turn a simple percentage rule into a clear API design. They look for correct request and response flow, sensible component boundaries, validation, database access, HTTP status handling, and practical edge cases. They also want the candidate to notice rounding, duplicate test cases, insufficient category inventory, and the trade-off between strict percentages and borrowing. The goal is to evaluate engineering judgment and clear communication, not memorization.

Interviewer may ask next
What would you do if one category does not have enough test cases for its assigned percentage?

I would keep the existing flow and use the shortage rule already shown in the design. The affected components are the Percentage Allocation Service and the Test Case Selector. Suppose the target count for regression is 20, but the available enabled and unique candidates are fewer than 20. With the normal strict behavior, the selection cannot satisfy the requested allocation, so the request returns 409. If allowBorrow=true, the design permits the shortage rule to be relaxed instead of returning that conflict. The POST /v1/test-case-selections endpoint, validation rules, Repository, Test Case Store, and successful 200 OK response path remain unchanged. The benefit of strict behavior is that the API does not silently pretend it met the requested percentages. The downside is that requests can fail when one category has limited inventory. Allowing borrowing improves completion, but the resulting category distribution may differ from the original percentages.

How would you make sure the returned test cases are valid and not duplicated?

I would keep that responsibility in the existing selection flow. The Repository supplies candidate cases from the Test Case Store, and the Test Case Selector applies the rules shown in the diagram. It selects enabled cases only and deduplicates cases across categories before building the final result. The Percentage Allocation Service still owns the target counts, so selection rules and percentage calculations remain separate responsibilities. If deduplication leaves a category with fewer usable cases than its target, the existing shortage behavior still applies: return 409 unless allowBorrow=true. The successful response path also stays the same. The Test Case Selector returns the selected cases and allocation summary to the REST Controller, which sends 200 OK JSON to the Testing Team / CI Job. The benefit is that each returned case appears only once. The downside is that deduplication can reduce the usable candidate pool and make shortages more likely.

29. How would you design an API to query logs between two timestamps?API DesignMediumGoogle

Question Details

Design a service API that adds services, records service calls, and returns all service calls between two times, with an optional filter by service.

Short Interview Answer (30-60 seconds)

At a high level, I would build one Java Log API Service with three operations. POST /services registers a service. POST /service-calls records a call after checking that the serviceId exists. GET /service-calls?start=...&end=...&serviceId=optional returns matching calls between two timestamps. I would validate the time range before querying the relational database and return clear errors for bad input. Indexes on called_at and (service_id, called_at) keep range queries efficient. The trade-off is extra storage and write work for those indexes.

Detailed Explanation

This API keeps a history of calls made by registered services. First, a client needs to add a service. Then it needs to record each call for that service. Later, the client can ask for all calls made between two chosen times. It may also limit the search to one service. The main challenge is making these searches fast while keeping the data correct. I would follow the same three flows shown in the diagram: register a service, record a service call, and query calls by time range.

Useful Questions to Ask the Interviewer
  • How many service-call records do we expect to store?
  • How large can one requested time range be?
  • Should results always be returned in called_at order?
  • Is serviceId the only optional query filter we need?
How would you design an API to query logs between two timestamps? diagram
How to Explain It in an Interview
1. Define the stored data

I would start with two related tables. The services table contains service_id, name, and created_at. The service_calls table contains log_id, service_id, called_at, status, and latency_ms.

The service_id in service_calls is a foreign key. This means every recorded call points to a registered service. It keeps service information separate from individual call records.

2. Register a service

The first flow creates a service. The API client sends POST /services {name} to the Service Registration API inside the Java Log API Service.

The Service Registration API inserts the service into the services table. After the insert succeeds, it returns 201 Created {serviceId} to the client.

The returned identifier can then be used when recording calls for that service.

3. Record a service call

The second flow records one call. The client sends POST /service-calls {serviceId, calledAt, status, latencyMs} to the Call Recording API.

Before storing the call, the API validates that the supplied serviceId exists in the services table. If the service exists, the API inserts the new row into service_calls. It then returns 201 Created to the client.

This validation prevents a call record from referring to an unknown service.

4. Query calls between two timestamps

The read endpoint is GET /service-calls?start=ts1&end=ts2&serviceId=optional.

The request reaches the Log Query API. The Validation + Query Logic checks the input and builds the database query. Both start and end are required. The optional serviceId limits the results to one service.

The query selects rows where called_at is between start and end. When serviceId is present, it also applies that service filter. The database orders the matching rows by called_at.

The matching log rows return to the Java Log API Service. The Log Query API then returns 200 OK logs[] to the client.

5. Handle invalid requests

The Validation + Query Logic rejects invalid input before executing the range query. Missing or invalid timestamps return 400. A request where start > end also returns 400.

If a supplied serviceId is unknown, the diagram specifies 404 unknown serviceId.

These responses make failures clear and prevent invalid requests from reaching the normal query path.

6. Index the main query patterns

The main database search uses called_at, sometimes together with service_id. I would therefore use the two indexes shown in the diagram: called_at and (service_id, called_at).

The called_at index supports time-range searches across all services. The composite (service_id, called_at) index supports searches limited to one service and one time range.

The benefit is faster log queries. The downside is extra database storage and additional index maintenance whenever new call rows are inserted.

Practical Complexity & Trade-offs

The design is simple because it has three API operations and two related tables. The main performance decision is indexing. The called_at index helps the database find records inside a time range without checking every row. The (service_id, called_at) index helps when the same query also filters by one service. The benefit is faster reads. The downside is extra storage and slightly more work for each insert because the database must update the indexes. Validation also matters. Rejecting missing timestamps or start > end avoids invalid range queries. Checking serviceId before recording a call keeps the relationship between the two tables valid. This design accepts a little more write cost to make the important log queries efficient.

Why Interviewers Ask This

Interviewers use this question to test whether you can turn a small requirement into clear API resources, request flows, validation rules, and database queries. They want to see correct HTTP methods and status codes, a sensible relationship between services and their call records, and a practical time-range query. They also check whether you recognize the need for indexes and can explain the trade-off between faster reads and additional storage and write work.

Interviewer may ask next
What would you change if the service_calls table became very large?

I would keep the same API contract and first improve the database query path behind it. The main affected endpoint is GET /service-calls?start=...&end=...&serviceId=optional, because a very large service_calls table makes range searches more expensive. I would keep validating start and end, apply the optional serviceId filter, and keep ordering matches by called_at. The existing called_at index remains important for searches across all services. The (service_id, called_at) index remains important when one service is requested. I would also clarify with the interviewer how large a time range callers are expected to request, because a very wide range can still match many rows. The registration and recording flows do not need to change. Each call still references a valid service. The main downside is that larger indexes consume more storage and require more work whenever POST /service-calls inserts a new row.

Why do you need both the called_at index and the composite (service_id, called_at) index?

I would keep both because the query API supports two different search patterns. When the client provides only start and end, the database searches calls across every service by called_at. The single-column called_at index directly supports that range search. When the client also provides serviceId, the (service_id, called_at) index better matches the query. It can narrow the rows to one service and then search that service's requested time range. The API behavior does not change. POST /services still registers services. POST /service-calls still checks that the service exists before inserting a log row. The GET endpoint still validates the timestamps and returns matching rows ordered by called_at. The benefit is efficient support for both query shapes. The downside is additional index storage and extra maintenance work for every new service_calls row.

30. How would you design an API to get the average of the latest k items with outliers?API DesignHardGoogle

Question Details

Design an API for computing the average of the latest k items in a running stream, including how you would handle outliers.

Short Interview Answer (30-60 seconds)

At a high level, I would keep a bounded recent window for each stream and calculate the average only when it is requested. Producers call POST /streams/{id}/items. After validation, the Stream State Manager appends the newest value to Per-Stream State and removes the oldest value when maxWindow is exceeded. Consumers call GET /streams/{id}/average?k=K&mode=iqr. The service selects the latest values, removes outliers, calculates the average, and returns metadata with 200 OK. The main trade-off is extra state and filtering work in exchange for a more useful outlier-aware result.

Detailed Explanation

The problem is to accept numbers continuously and later calculate an average using only the newest values. Some values may be unusually high or low, so including every value can make the average misleading. We therefore need a limited recent history for each stream, a clear rule for choosing the latest k values, and a way to remove outliers before calculating the result. The design also needs sensible behavior when fewer than k values are available. I would explain the solution by following the write path first and then the read path.

Useful Questions to Ask the Interviewer
  • What maximum value should we allow for k?
  • Should IQR always be the default outlier mode, or may clients choose another supported mode?
  • Is using all available values acceptable when the stream contains fewer than k items?
How would you design an API to get the average of the latest k items with outliers? diagram
How to Explain It in an Interview
1. Start with the API boundary

I would expose both operations through the REST Controllers inside the Java / Spring Boot service. A producer writes an item with POST /streams/{id}/items. The request arrives as HTTPS plus a JSON item. A successful write returns 201 Created to the Producer / Event Source.

A consumer asks for an average with GET /streams/{id}/average?k=K&mode=iqr. A successful query returns 200 OK JSON to the Consumer / API Client.

2. Validate requests before using stream state

Both paths go through Auth + Validation. For writes, it checks the JWT, schema, and idempotency information. Idempotency means a repeated write can be recognized instead of blindly creating another copy. For reads, it also validates that 1 <= k <= maxWindow.

After a valid write, the component sends the validated item to the Stream State Manager. After a valid read, it sends an authorized query to the Latest-K Selector. Invalid k or a bad payload produces 400, as shown in the diagram.

3. Keep a bounded window for each stream

The Stream State Manager appends the newest item to Per-Stream State. That state keeps a deque of the latest items, their sequence order, and maxWindow. A deque makes adding a new item and removing an old item efficient.

When the size becomes greater than maxWindow, the oldest item is evicted. This prevents the recent-item buffer from growing forever. The stream state also persists events to the Append Log / Durable Store. Write metrics are sent asynchronously to Observability, which records metrics, audit information, and errors.

4. Read the latest k available values

For a query, the Latest-K Selector reads from Per-Stream State. It uses min(k, available). This means a new stream can still return a result when it contains fewer than k items.

The selected latest values then move to the Outlier Policy Engine. The response can report both requestedK and availableCount, so the client can see how much data was actually present.

5. Remove outliers and build the result

The default outlier policy in the diagram is IQR. IQR means interquartile range. The engine keeps values inside [Q1 - 1.5*IQR, Q3 + 1.5*IQR] for the selected latest values. The design also shows an optional trimmed mean policy.

The filtered values go to the Average Builder. It calculates the average and returns the result plus metadata to the REST Controllers. The example response contains average, requestedK, availableCount, usedCount, excludedOutliers, and mode.

6. Explain the main trade-off

The benefit is bounded memory and an average that is less sensitive to extreme values. The downside is more state, persistence work, and filtering cost than a simple running average. Asynchronous observability keeps metrics and audit work outside the main business response path.

Practical Complexity & Trade-offs

The main design choice is to keep a bounded recent window instead of storing an unlimited stream in memory. The benefit is predictable memory use and fast access to recent values. The downside is that the service must manage state for every active stream. Using min(k, available) gives a useful answer when a stream is still small. IQR filtering makes the average less sensitive to extreme values, but it needs more computation than a plain average. Idempotency adds checking work, but it helps prevent repeated writes from changing the data incorrectly. Persisting stream events improves durability, but it adds storage work. Asynchronous metrics and audit recording reduce work on the main response path. We accept these costs because the API needs recent, explainable, outlier-aware results.

Why Interviewers Ask This

Interviewers ask this question to test whether you can turn a small mathematical requirement into a complete API design. They look for clear write and read paths, bounded state, correct handling of the latest k values, validation, outlier treatment, and useful response metadata. They also want to see judgment around idempotency, durability, observability, and failure behavior. A strong answer explains who owns each responsibility and communicates the trade-offs clearly.

Interviewer may ask next
What would you do if k becomes very large or the service has many active streams?

I would keep the same API and use maxWindow as the main bound. Auth + Validation already checks that 1 <= k <= maxWindow, so callers cannot request an unlimited number of values. Each Per-Stream State also keeps only its bounded deque of recent items. When a new value makes the deque larger than maxWindow, the Stream State Manager evicts the oldest value.

The read flow stays the same. The Latest-K Selector reads min(k, available), the Outlier Policy Engine filters those values, and the Average Builder produces the result. The write and query metrics continue going to Observability asynchronously.

With many streams, total state and filtering work still increase. The durable store also receives more persisted events. The main downside is therefore higher memory, storage, and computation cost. I would preserve the existing limit rather than allowing unlimited k, because the bounded window is what keeps the design predictable.

How would you handle a producer retrying the same write?

I would use the idempotency check already shown in Auth + Validation. The producer still sends the item through POST /streams/{id}/items. Auth + Validation checks the write request, including its idempotency information, before forwarding a validated item to the Stream State Manager.

The purpose is to recognize a repeated accepted write before it is appended again to Per-Stream State. That matters because a duplicate value would change the latest-item window and could therefore change future averages. New valid items still follow the same path through the Stream State Manager, Per-Stream State, the Append Log / Durable Store, and asynchronous observability.

The main downside is that recognizing repeated writes requires additional state and validation work. However, it protects the correctness of the stream when clients retry requests after network uncertainty. The read endpoint, outlier policy, average calculation, and response format remain unchanged.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.