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.
Identity, Image, and Privacy Notice
To respect individual privacy, some names, profile photographs, avatars, biographical details, and other identifying information displayed in this guide may be replaced with pseudonyms, licensed stock images, illustrative avatars, composite images, or representative descriptions. Unless a person is expressly identified as an actual contributor, a displayed name, image, or profile should not be understood as depicting or identifying a specific candidate, interviewer, employee, or other real individual. These representations are provided for editorial and illustrative purposes only and do not imply endorsement, employment, participation, or affiliation with this guide or any company mentioned in it. Any resemblance to an actual person is coincidental.
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.
Questions or comments?
Contact us for general questions, or share feedback, technical corrections, and comments with the community.
21. How would you design an API that takes content and determines which users can view it?API DesignMediumMeta
i Question Details
Design a privacy-check API that resolves visibility for a piece of content based on user relationships and rules.
Short Interview Answer (30-60 seconds)
At a high level, I would build a privacy-check API that decides which viewers may see a piece of content. The client sends POST /visibility/resolve through the API Gateway with the content ID, viewer IDs or a page token, and a JWT. The Java Spring Boot service validates the caller, checks a fresh cached decision, and otherwise loads the privacy rule, relationships, and allow or block lists. The Policy Engine evaluates visibility, with blocked users always denied. The main trade-off is speed versus freshness because caching reduces repeated work but requires a limited TTL.
Detailed Explanation
The problem is to decide which people can see one piece of content. The answer depends on who owns the content, how that owner chose to share it, how each viewer is related to the owner, and whether any viewer appears on a special allow or block list. We also want this decision to be fast because a feed may perform many checks. The diagram solves this by verifying the caller, checking cached results, reading the required privacy information, applying the rules, returning the allowed viewers, and recording the decision.
Useful Questions to Ask the Interviewer
Do we need to check a supplied list of viewers, or resolve the audience page by page?
Which privacy modes must we support?
How fresh must relationship and block-list changes be?
How to Explain It in an Interview
1. Start with the API boundary
I would expose POST /visibility/resolve through the API Gateway. The Client / Feed Service sends contentId, viewerIds[] or a page token, and a JWT over HTTPS. The API Gateway forwards that request into the Java Privacy Check API built with Spring Boot. The Controller receives the forwarded request and passes it to the Visibility Service. This keeps the HTTP entry point separate from the privacy decision logic.
2. Validate the caller before making a privacy decision
The Visibility Service asks the Auth Service to validate the JWT and the caller's permissions. The Auth Service returns an auth result to the Java service. Authentication establishes who the caller is, while the permission check determines whether that caller may use this operation. If this validation fails, the Java service fails closed. It does not continue with an untrusted or unauthorized caller. This is the main security decision shown in the diagram.
3. Check for a fresh cached decision
The Visibility Service looks in the Audience Cache for a cached decision or audience page. The cache returns a hit or miss. A fresh hit can avoid repeating the more expensive privacy lookups and policy evaluation. If the needed result is not available, the service continues with the source data. The cache is not treated as permanent truth. Cached audiences are stored with a TTL, which means each entry has a limited lifetime before it must be recomputed.
4. Load the content rule, relationships, and lists
On a cache miss, the Visibility Service reads the information needed for the decision. It asks the Content Metadata Store for the content owner and privacy rule. The store returns ownerId, rule, listId, and groupId. The service asks the Relationship Graph Service for each viewer's relationship to the owner, such as friend, follower, or friend-of-friend. It also reads the List & Blocklist Store, which returns custom-list members and blocked users. These are separate data sources because they own different parts of the privacy decision.
5. Evaluate the privacy rules
The Visibility Service sends the gathered information to the Policy Engine to evaluate the visibility rules. The supported examples are PUBLIC, FRIENDS, FRIENDS_OF_FRIENDS, CUSTOM_LIST, and ONLY_ME. The rule determines which viewers are eligible, while the block list acts as an overriding restriction. Blocked users are always denied. After resolving the audience, the result can be stored in the Audience Cache with a TTL so later checks can reuse it for a limited time.
6. Handle dependency failures safely
The diagram uses a fail-closed approach. If authentication fails, access is not granted. If the Relationship Graph Service times out, the Visibility Service may use the Audience Cache only when the cached result is still fresh. If no fresh cached result exists, the service denies until the relationship can be checked again. This favors privacy correctness over availability. It may temporarily deny someone who should have access, but it avoids exposing private content when relationship information is uncertain.
7. Return the result and record the decision
The Java service sends a decision event to the Audit Log as a separate logging flow. The Audit Log does not control the business response. The Java service returns allowedViewerIds[] and nextPageToken to the API Gateway. The gateway then returns the 200 OK response to the Client / Feed Service. The main design trade-off is performance versus freshness. Caching reduces repeated relationship and list lookups, but privacy changes may not be reflected until the cached result expires.
Practical Complexity & Trade-offs
The design keeps privacy logic in one Policy Engine instead of spreading rules across clients. The benefit is that every caller gets the same decision. Authentication and caller-permission validation happen before the privacy check, so an invalid caller cannot bypass the service. The Audience Cache improves performance because fresh decisions can be reused. The downside is freshness. A friendship, block, or privacy setting may change while an older cached result still exists. A limited TTL reduces that risk. The service also fails closed when important information is uncertain. If the relationship service times out, only a fresh cache entry may be used; otherwise access is denied. This is safer, but temporary dependency problems can reduce availability for valid viewers.
Why Interviewers Ask This
Interviewers use this question to test whether you can turn a privacy requirement into clear API boundaries and correct data flows. They want to see proper request and response modeling, authentication before access decisions, separation of policy logic from supporting data stores, and safe handling of blocked users. They also look for judgment around caching, dependency failures, and privacy correctness. A strong answer explains the performance-versus-freshness trade-off without adding unnecessary infrastructure.
Interviewer may ask next
What would you do if the Relationship Graph Service becomes slow or temporarily unavailable?
I would keep the same POST /visibility/resolve API and use the fallback shown in the design. The affected component is the Visibility Service because it normally asks the Relationship Graph Service for the viewer's relationship to the content owner. If that call times out, the service should use the Audience Cache only when a matching result is still fresh. If the cached result is missing or stale, the service should fail closed and deny until the relationship can be checked again. I would not guess that a viewer is a friend or extend an expired cache entry. Authentication, content metadata, list and block checks, policy evaluation, audit logging, and the response path remain unchanged when they are available. The main downside is availability. Some viewers who should be allowed may be temporarily denied during the outage, but private content is not exposed because the service used uncertain relationship data.
How would you handle a user being blocked after an audience result has already been cached?
I would keep blocked users as an overriding deny rule and rely on the cache's limited TTL to bound how long an older audience can remain reusable. The affected components are the List & Blocklist Store, Policy Engine, and Audience Cache. During a fresh evaluation, the Visibility Service reads the current block information, and the Policy Engine denies blocked users even if another privacy rule would otherwise allow them. The cached audience is not permanent. After its TTL expires, the service recomputes the result from the current content, relationship, and list data. The API contract does not change: the client still calls POST /visibility/resolve, and the service still returns allowedViewerIds[] with nextPageToken. Authentication and audit logging also remain unchanged. The downside is the freshness-versus-performance trade-off. A shorter TTL reduces the stale-decision window, but it causes more data reads and policy evaluations.
22. How would you design a canSee(owner_id, viewer_id) API for Facebook post privacy?API DesignMediumMeta
i Question Details
Design a privacy API that checks whether a viewer can access an owner's content.
Short Interview Answer (30-60 seconds)
At a high level, I would make canSee(owner_id, viewer_id) the central privacy decision API. The Feed Service gets the viewer identity from the JWT, then asks the Java Privacy API for an ALLOW or DENY decision. The API first checks a short-lived Decision Cache. On a miss, it reads the owner’s privacy rule and the viewer’s social relationship, including blocks and list membership. If required data cannot be checked and there is no cache hit, it fails closed with DENY. The trade-off is extra lookups for correctness, reduced by caching.
Detailed Explanation
This problem is about deciding whether one person may see another person’s content. The owner may share content with everyone, only friends, selected friends, or only themselves. We must also respect blocks and exception lists. The goal is to make one clear place responsible for that decision. The design should be fast because feeds need many checks, but it must not show private content by mistake. I will follow the diagram from the viewer request, through the privacy decision, and back to either the requested content or a denial.
Useful Questions to Ask the Interviewer
Do we need to support the exact privacy modes shown: PUBLIC, FRIENDS, FRIENDS_EXCEPT, SPECIFIC_FRIENDS, and ONLY_ME?
Should a block or explicit deny override normal friend-based access?
How fresh must privacy changes be before cached decisions become invalid?
How to Explain It in an Interview
1. Start with the request and trusted viewer identity
The request first goes from the Viewer App to the Content / Feed Service. The diagram shows an HTTPS GET for the owner’s content with a JWT. The Feed Service derives viewer_id from that JWT and calls canSee(owner_id, viewer_id) on the Java Privacy API. This matters because the privacy decision should use the authenticated viewer identity rather than trusting an arbitrary viewer_id supplied by the client. The Feed Service waits for an ALLOW or DENY result before deciding whether to return the owner’s content.
2. Check the short-lived decision cache first
The Java Privacy API first asks the Decision Cache for a cached decision. A cache hit returns the earlier ALLOW or DENY result and avoids the policy and social graph lookups. A cache miss means the API must calculate the decision again. After calculating a fresh decision, the API stores it in the Decision Cache with the short TTL shown in the diagram. The benefit is lower latency for repeated checks. The downside is that a cached answer may briefly be older than the latest privacy or relationship state.
3. Resolve the owner’s effective privacy rule
On a cache miss, the Policy resolver asks the Privacy Rule Store + Post Metadata for the effective audience rule. The response contains the privacy mode shown in the diagram: PUBLIC, FRIENDS, FRIENDS_EXCEPT, SPECIFIC_FRIENDS, or ONLY_ME. This component owns the stored privacy rule and post metadata. The Java Privacy API still owns the final access decision. List membership and social relationship information are checked separately through the Social Graph Service.
4. Read relationship, block, and list information
The Java Privacy API asks the Social Graph Service for relationship, block status, and list membership. The service can return information such as self, friend, follower, blocked, allow-list, or deny-list. The Decision engine combines these facts with the effective privacy rule. A follower alone does not satisfy a FRIENDS rule because that rule requires a friend relationship. A blocked viewer or a viewer on the deny-list is denied before normal public or friend-based rules are considered.
5. Apply the decision rules in the diagram’s order
The Decision engine follows the rule order shown in the diagram. First, if viewer_id equals owner_id, it returns ALLOW. Next, if the viewer is blocked or on the deny-list, it returns DENY. PUBLIC returns ALLOW. ONLY_ME is owner only. FRIENDS requires the viewer to be a friend. FRIENDS_EXCEPT requires a friend relationship and no deny exception. SPECIFIC_FRIENDS requires membership in the allow-list. If none of these rules allows access, the default result is DENY. This default-deny behavior prevents an unknown case from accidentally exposing private content.
6. Return the decision and fetch content only after ALLOW
The Java Privacy API returns ALLOW or DENY, with a reason, to the Content / Feed Service. If the result is ALLOW, the Feed Service asks the Content Store for the owner’s content. The Content Store sends the content back to the Feed Service. The Feed Service then returns 200 OK plus the content to the Viewer App. If the privacy result is DENY, the Feed Service returns 403 Forbidden instead. This keeps privacy evaluation separate from content storage and ensures the content is fetched only after authorization succeeds.
7. Record decisions and fail safely
The Java Privacy API also sends an asynchronous decision event to the Audit Log. This is a side flow and does not control the business response. For dependency failures, the diagram uses a fail-closed rule. If the privacy rule lookup or social graph lookup fails and no cache hit exists, the API returns DENY. The main trade-off is availability versus privacy safety. A dependency outage can temporarily hide content from an allowed viewer, but the system avoids exposing content when it cannot confirm that access is permitted.
Practical Complexity & Trade-offs
The benefit of this design is that one Java Privacy API owns the privacy decision. That keeps the privacy rules consistent for the Feed Service. The Decision Cache reduces repeated reads and improves latency when the same decisions are checked again. The downside is that cached decisions can become stale, so the diagram uses a short TTL. Reading both privacy data and social graph data gives the Decision engine the information it needs, but these dependency calls add latency. The design accepts that cost because privacy correctness is important. Failing closed is safer because missing rule or graph data cannot accidentally expose private content. The downside is lower availability during dependency failures. An allowed viewer may temporarily receive DENY until the required data becomes available again.
Why Interviewers Ask This
Interviewers ask this question to test whether you can turn privacy rules into a clear API boundary and a safe authorization flow. They want to see correct request and response modeling, trusted viewer identity handling, rule precedence, caching judgment, and failure behavior. They also check whether you separate the privacy decision from content storage and understand how social relationships, blocks, and exception lists affect access. A strong answer explains the trade-off between low latency, fresh decisions, availability, and preventing accidental data exposure.
Interviewer may ask next
What would you change if canSee traffic became very large and the Social Graph Service became a latency bottleneck?
I would keep the same Java Privacy API and privacy rules, but I would make careful use of the existing Decision Cache. The affected flow is the Java Privacy API checking the Decision Cache before calling the Privacy Rule Store + Post Metadata and Social Graph Service. A cache hit can return the previous ALLOW or DENY result without repeating those dependency lookups. I would keep the short TTL shown in the diagram because friendships, blocks, list membership, and privacy settings can change. On a cache miss, the API still performs the normal policy and graph checks before calculating a new decision and storing it. The fail-closed rule also stays unchanged. If a required lookup fails and no cache hit exists, the result is DENY. This maintains the same privacy behavior. The main downside is the freshness trade-off: a longer TTL reduces dependency traffic but increases the time an old decision may remain cached.
How should the design behave when an owner blocks a viewer after that viewer previously received an ALLOW decision?
The Decision engine should return DENY once the updated block state is used in a fresh decision. The affected components are the Decision Cache, Java Privacy API, and Social Graph Service. On a cache miss, the Java Privacy API asks the Social Graph Service for the current relationship and block information. The diagram’s rule order checks blocked or deny-list before PUBLIC and the friend-based rules, so a blocked viewer is denied. The rest of the flow remains unchanged: the Feed Service calls canSee, waits for ALLOW or DENY, and fetches content only after ALLOW. The existing short-lived Decision Cache creates the main trade-off. A previously cached ALLOW can remain until its short TTL expires. After expiration, the next decision uses the updated block state and returns DENY. A shorter TTL improves privacy freshness, but it also causes more calls to the privacy-rule and social-graph dependencies.
23. How would you design /get-privacy-settings, /post-message, and /check-privacy APIs for Facebook post privacy?API DesignMediumMeta
i Question Details
Design the API surface for creating posts, storing privacy settings, and checking access.
Short Interview Answer (30-60 seconds)
At a high level, I would keep post content separate from the privacy policy that controls who can see it. The client sends all three APIs through an API Gateway, which handles HTTPS, JWT authentication, and rate limiting. The Spring Boot application routes each request to the correct service. Privacy settings come from the User Privacy Settings DB, post content goes to the Posts DB, and effective privacy rules go to the Post Privacy Metadata DB. The trade-off is extra storage and service calls, but privacy checks stay clear and focused.
Detailed Explanation
This question asks us to design three simple actions around Facebook post privacy. A user needs to read available privacy settings, create a post with a visibility rule, and check whether another person can view that post. The main challenge is keeping the post and its visibility information consistent while making checks easy and fast. I would follow the diagram by storing the message separately from its privacy information. Then the system can decide who may see a post without changing or rewriting the message itself.
Useful Questions to Ask the Interviewer
Which privacy rules must we support: public, friends, friends except, only me, and custom audiences?
Should privacy checks use current friendship, block-list, and custom-audience information?
Should post creation and privacy checks be recorded for analytics?
How to Explain It in an Interview
1. Define the API boundary
I would place the Facebook Web / Mobile Client in front of an API Gateway. Every request first reaches that gateway. It uses HTTPS for encrypted traffic, JWT authentication to identify the caller, and rate limiting to control request volume. The gateway forwards requests into the Java Application Boundary. That boundary contains the Spring Boot Privacy & Post API with three entry points: /get-privacy-settings, /post-message, and /check-privacy.
2. Read privacy settings
For GET /get-privacy-settings?userId=123, the client sends the request through the API Gateway. The gateway forwards it to /get-privacy-settings, which uses the Privacy Settings Service. That service reads default privacy choices and custom lists from the User Privacy Settings DB. If named friend lists must be resolved, it also uses the Social Graph Service. The response returns through the Java API and API Gateway to the client as 200 privacy settings.
3. Create a post
For POST /post-message {authorId, message, privacyRule}, the gateway forwards the request to /post-message, which uses the Post Service. The Post Service asks the Privacy Settings Service to validate the privacy rule. It stores the post content in the Posts DB and stores the effective privacy policy in the Post Privacy Metadata DB. It also sends a post_created event to Audit Log / Analytics. The response returns through the gateway as 201 {postId, status}.
4. Check whether a viewer has access
For GET /check-privacy?viewerId=456&postId=p789, the gateway forwards the request to /check-privacy, which uses the Access Evaluation Service. That service loads the privacy policy from the Post Privacy Metadata DB. It loads the owner ID and post state from the Posts DB. It also asks the Social Graph Service for friendship, block-list, or custom-audience information when needed. It evaluates PUBLIC, FRIENDS, FRIENDS_EXCEPT, ONLY_ME, or CUSTOM. It sends a privacy_check event to Audit Log / Analytics. The response returns as 200 {allowed: true|false, reason}.
5. Keep data access and ownership clear
Inside the Java application, the JPA / Repository Layer provides the data-access abstraction used by the services. The Posts DB owns post content and post state. The Post Privacy Metadata DB owns the effective visibility policy. The User Privacy Settings DB owns user defaults and custom lists. The Social Graph Service supplies relationship information. Audit Log / Analytics receives activity events and is not part of the business response path.
6. Explain the trade-off
The main benefit is separation of responsibility. Privacy checks can read the stored policy without rewriting post content. The downside is more coordination because post creation writes content and privacy information separately, while access checks may read several sources. We accept that extra work because the privacy decision stays explicit and easier to reason about.
Time & Space Complexity
The main design choice is separating post content from privacy metadata. The benefit is that /check-privacy can read the visibility policy directly without changing the post. The downside is that /post-message must store two related pieces of information, so there is more coordination. Some privacy rules also need the Social Graph Service to check friendship, block lists, or custom audiences. That adds another dependency to the access path. HTTPS protects traffic, JWT authentication identifies the caller, and rate limiting controls request volume at the API Gateway. The JPA / Repository Layer keeps Java data access separate from business logic. Audit events improve visibility into post creation and privacy checks, but they add operational work. We accept these costs for clearer ownership and simpler policy evaluation.
Why Interviewers Ask This
Interviewers ask this question to see whether you can turn a product requirement into clear API boundaries and data ownership. They look for correct request and response directions, sensible HTTP contracts, and a clean separation between post content and privacy policy. They also test whether you understand authentication, rate limiting, relationship-based access checks, audit logging, and service responsibilities. The important skill is explaining the design and its trade-offs clearly instead of only naming technologies.
Interviewer may ask next
What happens if the Social Graph Service is unavailable during a privacy check?
Relationship-based privacy checks would lose information they need to make the decision. The affected flow is /check-privacy through the Access Evaluation Service to the Social Graph Service. Rules such as FRIENDS, FRIENDS_EXCEPT, or some CUSTOM audiences may depend on friendship, block-list, or audience data. I would keep the rest of the design unchanged. The Access Evaluation Service would still read the privacy policy from the Post Privacy Metadata DB and the owner ID or post state from the Posts DB. However, I would not guess an allow decision when required relationship data is missing. The approved diagram does not define a fallback, retry policy, or HTTP error response for this failure, so I would not invent one. I would tell the interviewer that the exact failure contract needs to be agreed separately. The downside is that relationship-based checks depend on Social Graph Service availability, but this avoids making a privacy decision from incomplete information.
Why store privacy metadata separately from the post content?
I would keep the separate Post Privacy Metadata DB because it makes the access path clearer. During /post-message, the Post Service stores the message content in the Posts DB and stores the effective privacy policy separately. Later, /check-privacy lets the Access Evaluation Service load that policy directly while separately reading the post owner and state. If the rule depends on relationships, it also asks the Social Graph Service for friendship, block-list, or custom-audience information. This keeps each data source focused on one responsibility. The correctness rule stays the same because the Access Evaluation Service still combines the stored policy with the other information required by that rule. The downside is extra coordination during post creation because related information is written to separate stores. It also means access checks may need several reads. We accept that cost because privacy policy remains independent from message content and is easier to evaluate.
24. How would you design a set with add, remove, and count?API DesignMediumMeta
i Question Details
Design a data-structure API that supports incrementing, decrementing, and counting values.
Short Interview Answer (30-60 seconds)
At a high level, I would design this as an in-memory counting set, or multiset, inside one Java JVM. The API exposes add, remove, and count. A HashMap<T, Integer> stores the frequency of each value. add increments the count. remove decrements it and removes the key when the count reaches zero. count returns zero for a missing value. The main correctness rule is that the map stores only positive counts. This gives average O(1) operations, with O(n) space for n distinct values.
Detailed Explanation
This problem asks us to keep track of how many copies of each value exist. A normal set only tells us whether a value exists. Here, adding the same value again should increase its number. Removing it should decrease that number. Asking for the count should return the current number. We want these operations to stay simple and fast. The diagram keeps everything inside one Java application and stores the values in memory. I would explain the same path from the caller, through CountingSet<T>, to the internal map and back.
Useful Questions to Ask the Interviewer
Should add and remove return the new count, as shown here?
Should removing a missing value leave the state unchanged and return 0?
Is this structure required only inside one JVM?
Do we need thread-safe behavior for concurrent callers?
How to Explain It in an Interview
1. Define the CountingSet API
I would start by saying this is a counting set, also called a multiset or bag. Unlike a normal set, it keeps a frequency for each value, so duplicates are allowed. The CountingSet<T> API exposes three operations: add(T value): int, remove(T value): int, and count(T value): int. The Client / Caller sends one of these requests to CountingSet<T>. The API performs the operation and sends the new count or current count back as a separate response. This gives callers one small and clear contract.
2. Store positive counts in a HashMap
Inside the Java Application, which is one JVM in the diagram, I would use HashMap<T, Integer> counts as the backing storage. The key is the value being tracked. The integer is its frequency. For example, a state can contain apple with count 2 and banana with count 1. The main invariant is simple: the map stores only positive counts. An absent key means count 0. We never keep zero or negative values in the map.
3. Implement add by incrementing the count
For add(value), CountingSet reads oldCount using counts.getOrDefault(value, 0). If the key does not exist, oldCount is
It calculates newCount = oldCount +
Then it stores counts.put(value, newCount). Finally, it returns newCount to the caller. For example, if apple currently has count 2, add(apple) stores 3 and returns 3.
4. Implement remove with two important decisions
For remove(value), the API first reads oldCount using counts.getOrDefault(value, 0). If oldCount is 0, the value is already absent. The state stays unchanged and remove returns 0. If oldCount is 1, removing one occurrence makes the logical count 0. The API removes the key from the HashMap and returns 0. If oldCount is greater than 1, it stores oldCount - 1 and returns that new count. These checks prevent zero or negative counts from remaining in the backing map.
5. Implement count as a read-only lookup
For count(value), the API reads counts.getOrDefault(value, 0) and returns that value. It does not modify the map. This makes missing values easy to handle because an absent key naturally produces 0. The response then goes from CountingSet<T> back to the Client / Caller as the current count.
6. Explain complexity and the design boundary
HashMap lookup, insertion, update, and removal are average O(1), so add, remove, and count are average O(1). Space is O(n), where n is the number of distinct values with positive counts. The benefit is that this design is small and fast for an in-process data structure. The trade-off is that the state lives in one JVM heap. Threads in that JVM can access the same object, but a different JVM or replica would not automatically share the same counts. The diagram intentionally does not add a database, cache, network service, or distributed coordination layer.
Time & Space Complexity
The benefit of this design is that each operation is simple and fast. HashMap gives average O(1) lookup, insert, update, and removal, so add, remove, and count also take average O(1) time. Space is O(n), where n is the number of distinct values with positive counts. Removing a key when its count reaches zero avoids keeping useless entries. The downside is that the HashMap lives inside one Java JVM. That is perfect for an in-memory data structure, but separate JVMs would not share the same state automatically. Another choice is returning the resulting count from add and remove. This is convenient for callers, but it makes the API contract more specific than simply returning success or failure.
Why Interviewers Ask This
The interviewer is checking whether you can turn a small requirement into a clean data-structure API. They want to see whether you choose a suitable backing structure, define clear method behavior, and handle edge cases correctly. Important cases include missing values and counts reaching zero. They also evaluate whether you can explain time and space complexity, maintain simple invariants, model the request and response clearly, and recognize the limits of an in-memory single-JVM design.
Interviewer may ask next
How would you make this CountingSet safe when many threads call add, remove, and count concurrently inside the same JVM?
I would keep the same CountingSet<T> API, but I would change the backing implementation so each update is atomic. A normal HashMap is not safe for concurrent modification. Separate get and put calls can also lose updates when two threads change the same value at the same time. I would use ConcurrentHashMap<T, Integer> and perform add and remove with atomic compute operations. add would calculate the next count from the current value. remove would return null from the compute function when the count reaches zero, which removes the key. count could use getOrDefault(value, 0). The original invariants stay the same: an absent key means zero, and zero or negative counts are never stored. The main downside is extra coordination overhead compared with a plain HashMap. I would add this only if concurrent access is a real requirement.
What should happen when remove is called for a missing value or when the last occurrence is removed?
I would keep the exact behavior shown in the design. If the value is missing, remove returns 0 and leaves the HashMap unchanged. This prevents negative counts and makes repeated remove calls predictable. If the current count is exactly 1, removing one occurrence makes the logical count 0. CountingSet removes the key completely instead of storing a zero, then returns 0. If the count is greater than 1, it stores oldCount - 1 and returns that new value. The affected flow is the remove branch between CountingSet<T> and HashMap<T, Integer> counts. This preserves the invariant that the map contains only positive counts. The downside is that the returned value 0 does not tell the caller whether the value was already absent or whether its final occurrence was just removed.
25. How would you design a simple HashMap or Dict GET function that returns a list for a given userID?API DesignMediumMeta
i Question Details
Design a small API around keyed lookup and list-return behavior for a user-centric data store.
Short Interview Answer (30-60 seconds)
At a high level, I would expose one GET endpoint that looks up a user ID and always returns a list for valid input. The API Client calls GET /users/{userId}/items. The GET Endpoint / Controller validates the userId, then calls UserListService. The service uses InMemoryUserListStore with map.getOrDefault(userId, emptyList). An existing key returns its List<String>, while a missing key returns 200 OK with an empty list. Invalid input returns 400 Bad Request. The benefit is a predictable contract. The trade-off is that the data is only stored in application memory.
Detailed Explanation
This question asks us to build a very small way to find items for one user. A caller gives us a user ID. We check that the ID is usable, look for that user in stored data, and return the user's items as a list. If the user has no entry in the stored map, we still return a successful answer with an empty list. If the ID itself is bad, we reject the request. The diagram keeps this design simple by separating request handling, lookup rules, and the in-memory user data.
Useful Questions to Ask the Interviewer
Should a missing userId return an empty list or an error?
Is an in-memory map enough for this exercise?
What formats should count as a valid userId?
How to Explain It in an Interview
1. Start with the API contract
I would start with one simple read endpoint. The API Client sends HTTP GET /users/{userId}/items to the GET Endpoint / Controller. The userId comes from the path. The diagram does not show a request body. For a valid request, the API returns a JSON response containing the user ID and an items list. For example, user u100 returns {"userId":"u100","items":["itemA","itemB"]} with 200 OK. This gives the client a simple list-return contract.
2. Validate the userId in the controller
The GET Endpoint / Controller owns path parsing, input validation, and the HTTP status. It reads the {userId} path parameter and checks whether the value is blank or malformed. If the value is invalid, the controller returns 400 Bad Request. The request does not continue to the service. If the userId is valid, the controller calls getItems(userId) on UserListService. This keeps HTTP input handling in the controller instead of mixing it with the lookup rule.
3. Keep the lookup rule in UserListService
UserListService owns the lookup behavior and the default empty-list policy. After receiving a valid userId, it asks InMemoryUserListStore for the user's list using map.getOrDefault(userId, emptyList). This is the important design decision. A missing key does not become an error. It becomes an empty list. The service therefore gives the controller a predictable response payload for every valid userId.
4. Store the keyed data in memory
InMemoryUserListStore owns the keyed in-memory user data. The diagram uses HashMap<String, List<String>>. The key is a userId such as u100. The value is a List<String>, such as ["itemA", "itemB"]. Another example is u200, which maps to ["itemC"]. For an existing key, the store returns its List<String> to UserListService. If the key is absent, the lookup follows the empty-list policy and returns an empty list.
5. Return the response through the same layers
The response travels back through the components in reverse order. InMemoryUserListStore returns List<String> to UserListService. The service returns the DTO or response payload to the GET Endpoint / Controller. The controller then sends the JSON response back to the API Client. A normal lookup returns 200 OK with the stored items. A userId not present in the map also returns 200 OK, but with items: []. A blank or malformed userId returns 400 Bad Request. The benefit is a simple and consistent API. The main trade-off is that the HashMap is in memory, so this diagram does not provide durable shared storage.
Why Interviewers Ask This
Interviewers ask this question to see whether you can turn a small keyed lookup into a clean API design. They want clear boundaries between HTTP request handling, lookup rules, and stored data. They also check whether you understand request and response direction, path parameters, status codes, validation, and missing-key behavior. A strong answer explains why malformed input returns 400 Bad Request while a valid but missing map key returns 200 OK with an empty list.
Interviewer may ask next
What happens when the userId is valid but does not exist in the HashMap?
I would return 200 OK with an empty items list, exactly as shown in the design. The GET Endpoint / Controller first validates the userId. Because the value itself is valid, the controller calls UserListService. The service then uses map.getOrDefault(userId, emptyList) through InMemoryUserListStore. Since the key is not present, the lookup returns an empty List<String>. That list travels back from the store to the service, then as the DTO or response payload to the controller, and finally as JSON to the API Client. This keeps the original contract simple: every valid request returns a list. The client does not need a separate error path just because the map has no key. The downside is that items: [] does not distinguish a known user with no items from a userId with no map entry. This design accepts that simpler behavior.
What happens when the client sends a blank or malformed userId?
I would return 400 Bad Request before performing the map lookup. The GET Endpoint / Controller owns this validation in the diagram. It parses the {userId} value from GET /users/{userId}/items and checks whether it is blank or malformed. If validation fails, the normal request path stops there. UserListService and InMemoryUserListStore are not called. The controller sends the error status back to the API Client. This keeps invalid HTTP input away from the lookup layers and preserves the responsibilities shown in the diagram. For valid input, nothing changes: the controller calls getItems(userId), the service applies the empty-list policy, and the store performs the keyed lookup. The downside is that the controller needs a clear and consistent definition of malformed input. That rule should stay simple and should not be mixed with the service's missing-key behavior.
26. What is the project you are most proud of?BehavioralMediumMeta
i Question Details
Describe the project you are most proud of and explain why it mattered.
Interview tip:
Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a meaningful Java project where you owned an important problem, made thoughtful technical decisions, worked closely with others, improved the reliability or usefulness of the system, and learned something that changed how you approach future projects.
Situation
In my last role, I worked on a Java service that handled an important business workflow. Over time, the service had become difficult to maintain because several responsibilities were mixed together, failures were hard to diagnose, and small changes could affect unrelated parts of the system. The project mattered because other teams depended on this service and needed it to behave reliably.
Task
I was responsible for improving the service without disrupting the existing workflow. My goal was to make the code easier to maintain, make failures easier to understand, and reduce the risk of future changes while keeping the behavior that other systems already depended on.
Action
I first studied the existing request flow and spoke with teammates who supported or consumed the service so I could understand the main pain points. I then separated the business logic from database and integration code so each part had a clear responsibility. I kept the existing external contract stable because changing it would have created unnecessary work for dependent teams. I added focused unit and integration tests around important paths before changing the implementation, which gave me confidence that the behavior remained consistent. I also improved exception handling and logging so failures showed useful context instead of generic errors. Rather than replacing everything at once, I changed the service in small steps and reviewed each step with the team. That approach made the work easier to validate and gave other developers a chance to challenge my assumptions. I documented the main design decisions and explained the new structure to the team so future changes would not depend only on my knowledge.
Result
The service became easier for the team to understand, test, and change, and production issues were easier to investigate because the failure paths were clearer. Other developers were able to work on the service with more confidence. I am most proud of the project because the value was not just new code. I helped turn a fragile part of the system into something the team could maintain more safely. I also learned that improving an existing system usually works best when I understand its users first, protect important contracts, and make changes in small, verifiable steps.
Why Interviewers Ask This
Interviewers ask this question to understand what kind of work the candidate values and how they define meaningful impact. A strong answer shows ownership, sound judgment, technical responsibility, collaboration, and the ability to explain why a project mattered beyond simply completing assigned work.
Interviewer may ask next
Why did you improve the existing service instead of replacing it completely?
I chose incremental improvement because other teams already depended on the service and its existing behavior. Replacing everything at once would have increased delivery and compatibility risk. By adding tests first and improving one area at a time, I could make the design better while continuously checking that dependent workflows still worked.
What would you do differently if you worked on a similar project now?
I would involve the teams that depend on the service even earlier and document the most important operational problems before changing the design. On this project, those conversations were very useful once they started. Beginning them sooner would help me confirm priorities earlier and make sure the technical work is focused on the problems that matter most.
27. Tell me about a project that you're not proud of and why.BehavioralMediumMeta
i Question Details
Describe a project you are not proud of and what you would do differently.
Interview tip:
Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a project where your implementation worked but the design became harder to maintain, explain the decisions you made, how you communicated the problems, what you changed, and what you would do differently now.
Situation
In my last role, I worked on a Java service that handled business rules for an internal application. We had a tight delivery schedule, and I focused too much on getting the requested features working quickly. The service passed our functional tests, but the code became difficult to understand because several responsibilities were placed inside the same classes.
Task
I was responsible for implementing a large part of the service and keeping the code reliable. I also needed to make sure other developers could safely maintain it. I completed the required functionality, but I later realized that I had not given enough attention to code structure and long term maintainability.
Action
Once the problems became clear, I took ownership instead of treating the working functionality as enough. I reviewed the classes I had written and identified places where validation, business logic, database access, and response construction were mixed together. I discussed the issue with my team and explained that continuing with the same structure would make future changes harder and increase the chance of defects. I then separated the responsibilities into smaller Java components with clearer purposes. I added focused unit tests around the business rules before changing the structure so I could refactor safely. I also asked another developer to review the new design because I wanted feedback on whether the responsibilities were clear to someone who had not written the original code. The main reason I am not proud of the original project is that I recognized these design concerns later than I should have. If I handled the project again, I would spend more time at the beginning identifying the main responsibilities, agreeing on a simple structure with the team, and reviewing maintainability during development instead of only checking whether the features worked.
Result
The service became easier for the team to understand and modify, and later changes could be made with less risk of affecting unrelated logic. The project taught me that working software is only part of a good result. I also need to think about readability, ownership boundaries, testing, and future changes while I am building the solution, not after complexity has already grown.
Why Interviewers Ask This
Interviewers ask this question to see whether a candidate can evaluate their own work without becoming defensive or blaming others. A strong answer shows self awareness, ownership, sound technical judgment, willingness to correct mistakes, and the ability to turn an imperfect experience into better engineering habits.
Interviewer may ask next
What would you do differently if you started that project again?
I would identify the main responsibilities before writing most of the implementation and agree on a simple structure with the team. I would also review maintainability during development so that mixed responsibilities and growing complexity are addressed early instead of after the service is already difficult to change.
How did you make sure the refactoring did not break existing behavior?
I added focused unit tests around the existing business rules before changing the structure. Those tests gave me a safety check while I separated responsibilities, and I also used code review so another developer could verify both the behavior and the clarity of the new design.
28. Tell me about a time you disagreed with someone and how you resolved it.BehavioralMediumMeta
i Question Details
Describe a disagreement, how you handled it, and the outcome.
Interview tip:
Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a disagreement about a technical decision, explain your responsibility, show how you listened to the other view, used evidence to compare options, worked toward a shared decision, and explain the outcome and what you learned.
Situation
In my last role, I was working on a Java service that called another internal service for data needed during a request. A teammate wanted to add automatic retries for every failed call because they believed this would make the service more reliable. I disagreed because retrying every failure could increase traffic during an outage and make the problem worse.
Task
I was responsible for implementing the client logic for this integration. I needed to help the team choose an approach that improved reliability without creating unnecessary load or turning a temporary dependency problem into a larger incident.
Action
I first asked my teammate to explain the cases they wanted the retry logic to solve. I made sure I understood their concern instead of treating the discussion as a debate I needed to win. We agreed that short network problems could recover quickly, but failures such as invalid requests would not improve with another attempt. I then suggested that we separate temporary failures from permanent failures. I reviewed the response codes and failure behavior of the dependency and shared a simple proposal with the team. For temporary failures, I recommended a small number of retries with increasing wait time between attempts. For permanent failures, I recommended returning the error without retrying. I also suggested setting clear time limits so our service would not keep waiting when the dependency was unhealthy. We discussed the tradeoffs together and tested the behavior with simulated failures. The test showed that selective retries could recover from brief problems without repeatedly sending requests for errors that could not succeed. My teammate agreed with the revised approach, and we implemented it together.
Result
We resolved the disagreement without making it personal and reached a design that both of us supported. The service handled temporary dependency failures more safely while avoiding unnecessary retry traffic for permanent errors. I learned that technical disagreements are easier to resolve when I first understand the other person's goal, then use concrete failure cases and tests to evaluate the options instead of relying only on opinions.
Why Interviewers Ask This
Interviewers ask this question to understand how a candidate handles conflict while still working well with others. A strong answer shows that the candidate listens carefully, explains concerns respectfully, uses evidence instead of ego, works toward a shared decision, and can maintain a productive relationship after the disagreement is resolved.
Interviewer may ask next
How did you handle it when your teammate initially preferred the other approach?
I focused on understanding why they wanted retries before presenting my concern. Once we agreed on the reliability problem we were trying to solve, I compared specific failure cases with them and suggested testing the options. That made the discussion about system behavior rather than whose idea was better.
What would you do differently if you faced a similar disagreement now?
I would bring concrete failure examples into the discussion even earlier. In this case, reviewing temporary and permanent failures helped us reach agreement quickly. Starting with those examples sooner would make the tradeoffs clearer and reduce the time spent discussing the approaches in general terms.
29. How do you resolve conflict with your manager and team manager?BehavioralMediumMeta
i Question Details
Describe how you would handle conflict between a manager and a team manager.
Interview tip:
Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a situation where your manager and another team manager gave conflicting priorities, how you clarified the goals, brought the right people together, proposed a practical solution, and helped the teams agree on a clear path forward.
Situation
In my last role, I was working on a Java service that supported a feature needed by another team. My manager wanted me to focus first on improving the reliability of the service, while the other team manager wanted the new feature completed as soon as possible. Both requests were reasonable, but following either one without discussion could have caused problems for the other team.
Task
I was responsible for the Java changes, so I needed to resolve the conflicting priorities without taking sides. My goal was to understand the business need behind each request, explain the technical impact clearly, and help both managers agree on an order of work that the development teams could follow.
Action
I first spoke with each manager separately so I could understand the reason behind the priority instead of assuming that one request was more important. My manager was concerned that adding more code before fixing existing reliability issues could increase production risk. The other team manager was concerned that delaying the feature would block work already planned by that team. I then reviewed the affected Java service and identified which reliability work was necessary before the feature could be added safely. I separated those items from improvements that could wait. I shared this information with both managers using simple language and explained the impact of each option. Instead of arguing for one side, I proposed that I complete the critical reliability fixes first, then implement the feature, and schedule the remaining improvements afterward. I asked both managers to confirm that this order addressed their main concerns. Once they agreed, I documented the priority and communicated it to the developers involved so everyone worked from the same decision. I also kept both managers informed when the reliability work was complete and when the feature work started.
Result
The managers agreed on the shared priority, and the team moved forward without continued conflict or unclear direction. We addressed the important reliability concerns before adding the requested feature, while the other team received a clear plan for when its dependency would be ready. I learned that conflicts between managers are easier to resolve when I focus on the goals behind their positions, provide clear technical facts, and help them make one visible decision together.
Why Interviewers Ask This
Interviewers ask this question to understand how a candidate handles conflicting direction, disagreement, and professional relationships. A strong answer shows that the candidate does not take sides or ignore the conflict. Instead, the candidate listens, uses facts, communicates technical impact clearly, helps people find a shared priority, and takes ownership of carrying out the agreed decision.
Interviewer may ask next
Why did you speak with each manager separately before bringing them together?
I wanted to understand each manager's concern without turning the first discussion into a debate. That helped me identify that one manager was focused on production reliability while the other was focused on a team dependency. Once I understood both goals, I could present a solution that addressed the important part of each request.
What would you have done if the two managers still could not agree?
I would have clearly documented the options, technical risks, and effect on each team, then asked the managers to make the priority decision at the appropriate leadership level. I would not make a business priority decision on their behalf. My responsibility would be to provide accurate technical information, make the tradeoffs clear, and follow the final decision once it was agreed.
30. How do you convince another team to complete the task or initiative you are working on?BehavioralMediumMeta
i Question Details
Describe how you would influence another team to support or finish an initiative.
Interview tip:
Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a project where your Java service depended on another team, how you understood their priorities, explained the shared impact, reduced the effort required from them, agreed on clear responsibilities, and worked with them until the dependency was completed.
Situation
In my last role, I was working on a Java service that needed data from an API owned by another engineering team. Our feature could not be completed until that team added a small change to their API. They had their own priorities, so our request was not getting attention.
Task
I was responsible for the integration on our side. My goal was to get the dependency completed without simply escalating the issue or treating the other team as a blocker. I needed to understand their situation, show why the work mattered to both teams, and make the request as easy as possible for them to complete.
Action
I first spoke with the engineer responsible for their API and asked about their current priorities and concerns. I learned that the request was competing with planned work and that some details in our original request were unclear. I then prepared a short description of the exact API change we needed, the expected request and response, and how our Java service would use it. I also explained the impact of the delay in practical terms, including which part of our release depended on their work. Instead of asking them to solve the entire integration problem, I reduced the request to the smallest change that their team needed to own. I offered to handle the client changes, integration tests, and validation on my side. We reviewed the proposed contract together so they could raise concerns before implementation. After we agreed on the approach, I documented the responsibilities and stayed available for questions. I followed up respectfully based on the timeline we had agreed on rather than repeatedly asking for status. When they started the work, I tested the integration quickly and shared clear feedback so we could resolve issues together.
Result
The other team completed the API change, and we finished the integration without creating unnecessary conflict or requiring a management escalation. The collaboration also improved because both teams had a clearer understanding of ownership and expectations. I learned that influencing another team works best when I first understand their priorities, explain the shared value, reduce unnecessary work for them, and make the path to completion clear.
Why Interviewers Ask This
Interviewers ask this question to understand how a candidate influences people without direct authority. A strong answer shows that the candidate can understand another team's priorities, communicate shared impact, remove unnecessary friction, create clear ownership, and build cooperation instead of relying immediately on escalation.
Interviewer may ask next
What would you have done if the other team still refused to prioritize the API change?
I would first confirm whether the issue was priority, technical risk, or lack of capacity. I would look for another way to reduce the scope or adjust our implementation. If the dependency still blocked an important commitment, I would bring the facts, options, and impact to the appropriate leads so they could make a priority decision. I would treat escalation as a way to resolve competing priorities, not as a complaint about the other team.
Why did you reduce the request instead of asking the other team to complete the full integration?
I wanted each team to own the work closest to its system. Their team understood their API, while I understood the Java service consuming it. By asking them only for the smallest API change and taking responsibility for the client code, integration tests, and validation, I reduced their effort and made the request easier to prioritize. It also gave both teams clear ownership.
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.