Scenario Based Questions Interview Questions | JiQuest

add

#

Scenario Based Questions

Java scenario-based interview preparation

105 professional Java scenario questions with answer frameworks.

Practice system design, core Java, concurrency, APIs, persistence, and cloud topics with answers that focus on decisions, trade-offs, and production-ready reasoning.

105Scenarios
12Topic areas
5-stepAnswer method
Client API Service Queue Worker Cache Database

How to answer scenario questions

Start with the problem shape, then move into Java choices, safeguards, and measurable trade-offs.

ClarifyAsk scale, latency, data size, consistency, and failure behavior.
ModelName the entities, states, operations, and boundaries.
ChooseSelect Java collections, concurrency utilities, APIs, and patterns.
ProtectAdd validation, thread safety, exceptions, retries, and cleanup.
EvolveDiscuss tests, monitoring, scaling, and future changes.
Lead with assumptionsState expected scale, latency, data volume, and consistency needs.
Name the Java toolsMention the collection, concurrency utility, API, or framework you would use.
Call out failure modesCover duplicate requests, race conditions, retries, partial failures, and cleanup.
Close with proofExplain tests, metrics, logs, and production checks that validate the design.

Topics

Questions and answers

Use the prompts below as interview drills. Each answer keeps the focus on practical design choices, risks, and clear Java terminology.

Object-Oriented Design

1. Design a media player that supports MP3, WAV, MP4, and future formats.

Use a MediaFile interface for common behavior and concrete classes for format-specific decoding. Add a factory that chooses the implementation by MIME type or extension. Prefer composition for features such as subtitles, DRM, and streaming because deep inheritance becomes rigid.

interface MediaFile { void play(); void pause(); }
class Mp3File implements MediaFile { public void play(){} public void pause(){} }

2. Design a parking lot where bikes, cars, and trucks use different spot sizes.

Model Vehicle, Spot, Ticket, Gate, and ParkingLot. Use enums for vehicle and spot types. Keep allocation in a strategy so the rule can later become nearest spot, EV-first, reserved-first, or cheapest spot. Use locking or database constraints so two gates cannot assign the same spot.

3. Design a to-do list app with sharing and reminders.

Use User, TodoList, Task, Reminder, and Permission. Encapsulate task status transitions. For sharing, check permissions in the service layer. For reminders, store due time and send jobs to a scheduler or queue. Add optimistic locking to avoid overwriting collaborators.

4. Design a library management system.

Core entities are Book, BookCopy, Member, Loan, and Fine. Separate title metadata from physical copies because one title can have many copies. A loan owns checkout and due dates. Add reservation queues for unavailable books and policies for maximum loans and late fees.

5. Design an elevator system for a building.

Model elevators, floors, requests, direction, and scheduler. The scheduler assigns requests based on direction, current floor, capacity, and priority. Keep elevator state isolated and update it through commands. Mention safety rules: doors, overload, emergency stop, maintenance mode, and event logging.

6. Design a vending machine.

Use states such as idle, accepting money, dispensing, and out of service. Model product inventory, payment, selection, and change. The state pattern keeps behavior clear when a user inserts money, cancels, or selects an unavailable item. Inventory updates must be atomic.

7. Design a chess game in Java.

Model Board, Square, Piece, Move, and Game. Each piece can validate its own movement pattern, while the game validates check, checkmate, turn order, and special moves. Use immutable move history for undo and replay.

8. Design an ATM.

Use states for card inserted, authenticated, choosing transaction, dispensing cash, and ending session. Separate authentication, account service, cash dispenser, and audit logging. All money-moving operations need transaction boundaries and idempotency so a timeout does not debit twice.

9. Design a movie ticket booking system.

Model movie, theater, show, seat, booking, and payment. Hold seats temporarily while payment is in progress, then confirm or release after timeout. Use optimistic locking or seat-status constraints so two users cannot book the same seat. Show seat map from read-optimized data.

10. Design a food delivery order lifecycle.

Use order states: placed, accepted, preparing, picked up, delivered, cancelled. Services should validate legal transitions. Payment, restaurant acceptance, delivery assignment, and notifications can be event-driven. Add compensation when restaurant rejects after payment authorization.

Collections and Data Structures

11. Implement a queue that also supports random access.

Use ArrayList with a head pointer. Enqueue appends, dequeue increments head, and random access reads data.get(head + index). Compact occasionally. ArrayDeque is better for pure queue behavior but not random access.

12. Design a frequently updated leaderboard.

Use a HashMap for current score lookup and a TreeSet for sorted ranking. On update, remove old score entry and insert updated one. For distributed systems, Redis sorted sets are a strong choice.

13. Remove duplicate transactions efficiently.

First define duplicate identity. If transaction id is reliable, use HashSet. If domain fields define equality, create a key record. Use LinkedHashSet when preserving input order matters.

Set<String> seen = new HashSet<>();
List<Tx> unique = txs.stream().filter(t -> seen.add(t.id())).toList();

14. Keep only the latest 1000 user logs in memory.

Use ArrayDeque as a bounded buffer. Add to the tail and remove from the head when full. For concurrent writers, guard with a lock or use a concurrent queue and strict size policy.

15. Design priority message processing.

Use PriorityQueue or PriorityBlockingQueue. Include priority and sequence number so same-priority messages remain fair. Add retry count and dead-letter handling for failures.

16. Find the first non-repeating character in a stream.

Use a frequency map plus a queue of candidates. Each new character updates count and enters the queue. Remove queue head while its count is greater than one. The head is the current first non-repeating character.

17. Design an LRU cache.

For a simple single-threaded cache, use LinkedHashMap with access order and override removeEldestEntry. For concurrent production use, prefer Caffeine.

new LinkedHashMap<K,V>(16, .75f, true) {
  protected boolean removeEldestEntry(Map.Entry<K,V> e) { return size() > 1000; }
};

18. Group orders by customer and calculate total amount.

Use streams with groupingBy and summingDouble. For money, prefer BigDecimal and a reducing collector. Keep null customer ids out or map them to an explicit bucket.

19. Design a browser history feature.

Use two stacks: back and forward. Visiting a new page pushes current page to back and clears forward. Back pops from back and pushes current to forward. Forward does the reverse.

20. Pick a collection for storing unique users in insertion order.

Use LinkedHashSet. It gives uniqueness like HashSet and preserves insertion order. If sorted order is needed, use TreeSet with a comparator.

Exception Handling

21. Design custom exceptions for an e-commerce system.

Create a base ShoppingException with error code, safe message, and cause. Add inventory, payment, pricing, and validation subclasses. Map exceptions to consistent API responses at the boundary.

22. Handle database and file exceptions in one service method.

Catch low-level exceptions near the boundary, add context, and translate to domain exceptions. Do not expose SQL or file paths to users. Use try-with-resources so handles are closed safely.

23. Ensure rollback in a banking transfer.

Debit, credit, ledger, and audit must be one transaction. Commit only after every step succeeds. Roll back on any failure. Add idempotency key so retries do not duplicate transfer.

try {
  connection.setAutoCommit(false);
  debit(); credit(); audit();
  connection.commit();
} catch (Exception ex) {
  connection.rollback();
  throw ex;
}

24. Capture exceptions from worker threads.

For Thread, use UncaughtExceptionHandler. For ExecutorService, inspect Future.get(). For CompletableFuture, use handle or exceptionally. Always include job id and correlation id in logs.

25. Design client-friendly API error handling.

Return stable error codes, message, trace id, and retryable flag. Clients should depend on codes, not message text. Map validation to 400, conflicts to 409, rate limits to 429, and unknown failures to 500.

26. Decide checked vs unchecked exception for payment failure.

If callers are expected to recover, use a declared domain exception. If the failure is a programming/configuration issue, use unchecked. In Spring, runtime exceptions trigger rollback by default, so be explicit about transaction behavior.

27. Handle retryable external API errors.

Retry only transient failures such as timeout or 5xx. Use exponential backoff with jitter. Do not retry validation errors or payment declines. Add idempotency so retry does not duplicate side effects.

28. Log exceptions without leaking secrets.

Log technical details for operators, but redact passwords, tokens, card numbers, and personal identifiers. Return safe user messages. Store trace ids so support can connect a user report with internal logs.

29. Handle partial failure in a batch import.

Do not fail the whole import for one bad row unless business requires it. Store valid rows, collect rejected rows with reason, and return a summary. Use transaction per batch and checkpoint progress.

30. Design global exception handling in Spring Boot.

Use @ControllerAdvice to map domain and validation exceptions to consistent response DTOs. Include trace id and safe message. Keep controllers clean and avoid repeated try-catch blocks.

Multithreading and Concurrency

31. Implement producer-consumer.

Use a bounded BlockingQueue. Producers call put, consumers call take. The queue handles waiting safely and provides backpressure. Use a poison pill or shutdown flag for graceful termination.

32. Allow many readers and one writer.

Use ReentrantReadWriteLock. Readers share the read lock, while writers get exclusive access. Release locks in finally. Avoid remote calls while holding locks.

33. Aggregate results from multiple threads.

Use Callable tasks and Future, or CompletionService to process whichever finishes first. Merge results after completion or use thread-safe accumulators. Define what happens when one task fails.

34. Build a token-bucket rate limiter.

Keep an atomic token count and refill using ScheduledExecutorService. Each request consumes one token or receives 429. For multiple instances, use Redis or gateway-based rate limiting.

35. Execute tasks with dependencies.

Represent work as a DAG. Start nodes with no dependencies. When a task completes, decrement dependent counts and submit newly ready tasks. Detect cycles before running. CompletableFuture works well for smaller dependency graphs.

36. Prevent race conditions in bank balance updates.

Use database row locking, optimistic versioning, or atomic update queries. In memory, use locks or atomic classes, but money should usually be protected by transactional database updates. Always verify sufficient funds inside the transaction.

37. Design a thread pool for image processing.

Use ThreadPoolExecutor with bounded queue. CPU-heavy work should use roughly CPU-count threads. Add rejection policy, timeouts, and metrics for queue depth. Do not use unbounded queues because they hide overload.

38. Stop a long-running worker safely.

Use interruption and cooperative cancellation. The worker should check interrupted status or a volatile flag and clean up resources. Avoid deprecated unsafe stop methods.

39. Share counters across many threads.

Use LongAdder for high-contention counters such as metrics. Use AtomicLong when you need exact atomic read-update semantics. LongAdder is optimized for frequent increments.

40. Avoid deadlocks.

Use consistent lock ordering, reduce lock scope, avoid nested locks, and never call external code while holding a lock. Use timed locks when appropriate and monitor blocked threads.

I/O, Files, and Serialization

41. Process a 10GB file without loading it fully.

Use streaming with BufferedReader or NIO channels. Process line by line or chunk by chunk. Keep only small batches in memory. Track rejected rows and checkpoint progress.

42. Design asynchronous file logging.

Application threads enqueue log events to a bounded queue. A writer thread drains and writes batches. Choose overflow policy: block, drop debug logs, or fail. In production, prefer Log4j2 or Logback async appenders.

43. Serialize the same object differently for REST and messaging.

Use DTOs and serializer strategies. REST may use JSON with public fields, while messaging may use compact event DTOs. Version message payloads and avoid exposing internal domain objects directly.

44. Watch a directory for new files.

Use WatchService, but handle duplicate events and partially copied files. Process only after size stabilizes or a marker file appears. Move files through incoming, processing, processed, and failed folders.

45. Stream video/audio to clients.

Use chunked reads, NIO, or Netty. Support range requests, backpressure, timeouts, and client disconnects. Do not load the whole media into memory. Use CDN for large-scale delivery.

46. Upload files securely.

Limit size, validate extension and magic bytes, generate safe filenames, store outside web root, and normalize paths. Scan files when required. Never trust the original filename.

47. Read CSV with malformed rows.

Use a parser library rather than splitting by comma manually. Validate each row. Store bad rows with reason and continue if business allows. Return summary counts for imported, rejected, and skipped rows.

48. Export a million records to CSV.

Stream database results and write rows incrementally. Use pagination or cursor queries. Flush periodically. Avoid building one huge string. For web downloads, stream response body.

49. Design a retryable file processor.

Use file states: incoming, processing, done, failed. Record attempts and last error. Retry transient failures with backoff. Move permanently bad files to failed with reason.

50. Version serialized events.

Add schema version to the payload. Consumers should ignore unknown fields. Avoid removing fields abruptly. Use compatibility tests so producers do not break consumers.

Memory and JVM

51. Diagnose a memory leak.

Watch heap after full GC. Capture heap dump and inspect dominator tree. Common causes are static maps, unbounded caches, listeners not removed, ThreadLocal values, and growing queues. Fix by releasing references and bounding storage.

52. Reduce GC overhead in a high-throughput service.

Reduce allocation rate in hot paths. Reuse buffers carefully, avoid temporary objects, batch operations, and use primitive collections when needed. Tune GC only after measuring allocation and pause times.

53. Handle OutOfMemoryError.

Configure heap dump on OOM, restart the process through orchestration, and investigate heap, metaspace, direct memory, or thread exhaustion. Do not continue normal business processing after OOM.

54. Explain stack vs heap for object allocation.

Each thread has its own stack with method frames and references. Objects live on the shared heap. Stack overflow comes from deep recursion; heap OOM comes from too many retained objects.

55. Use weak references correctly.

Use weak references when metadata should not keep an object alive. Use WeakHashMap for keys that can disappear. Do not use references as a replacement for closing resources.

56. Tune JVM heap for a container.

Set container memory limits and choose JVM max heap so there is room for metaspace, thread stacks, direct buffers, and native memory. Monitor GC, RSS, and OOM kills.

57. Optimize memory for millions of integers.

Avoid List<Integer> if boxing cost is high. Use int[] or primitive collections. Measure memory before and after. This can dramatically reduce heap usage.

58. Detect classloader leaks.

Classloader leaks happen when app classes are still referenced after redeploy. Check static references, ThreadLocals, JDBC drivers, and threads. Heap dump will show old classloaders retained.

59. Choose between G1 and ZGC.

G1 is a strong default for balanced throughput and pause control. ZGC is useful for very low pause goals on modern Java. Choose based on latency SLO, heap size, and measurements.

60. Handle large object allocation spikes.

Find allocation sources with a profiler. Stream large payloads, cap request sizes, reuse buffers carefully, and avoid building giant intermediate strings or byte arrays.

Java 8+ and Functional Style

61. Filter, transform, and aggregate orders.

Use streams for a clear pipeline: filter paid orders, map to amount, then reduce or collect. Keep lambdas side-effect free and extract methods when logic grows.

double total = orders.stream()
  .filter(Order::isPaid)
  .mapToDouble(Order::amount)
  .sum();

62. Compose validation rules.

Use Predicate or a custom functional interface with and/or default methods. This keeps rules reusable and testable.

63. Use Optional in legacy null-heavy code.

Use Optional as a return type when absence is meaningful. Do not call get() blindly. Use map, flatMap, and orElseThrow.

64. Run independent API calls in parallel.

Use CompletableFuture with a dedicated executor. Combine results with thenCombine. Add timeout and exception handling.

65. Group employees by department.

Use Collectors.groupingBy. For counts, add counting(). For summaries, use summingInt or summarizingDouble.

66. Refactor nested loops into streams.

Use flatMap when one item expands into many. Keep loops when stream code becomes less readable or has complex control flow.

67. Use default methods for API evolution.

Default methods let you add interface behavior without breaking old implementers. Use them only when behavior can be derived from existing methods.

68. Use method references in a pipeline.

Method references improve readability when the lambda only calls one method. Use User::getEmail instead of u -> u.getEmail().

69. Parallel stream for CPU-heavy work.

Use parallel streams only for large independent CPU-bound work. Avoid blocking I/O and shared mutable state. Measure performance because parallelism has overhead.

70. Convert checked exceptions inside streams.

Streams do not handle checked exceptions cleanly. Wrap operations in helper methods or use normal loops when exception handling is central to readability.

Design Patterns

71. Use Factory for payment processors.

Factory hides creation of card, UPI, wallet, or netbanking processors. Services depend on PaymentProcessor, not concrete classes. Register creators in a map to avoid large switches.

72. Use Strategy for discounts.

Define DiscountStrategy and implementations for festival, loyalty, coupon, and no discount. Choose the strategy from customer and campaign context.

73. Use Observer for order events.

Publish OrderCreated and let email, inventory, and analytics listeners react independently. Use queues for distributed observers. One listener failure should not break unrelated listeners unless required.

74. Use Builder for complex request objects.

Builder makes construction readable and supports immutable objects. Validate required fields in build(). Avoid builder for tiny classes.

75. Use Adapter for third-party APIs.

Wrap third-party client responses behind your own interface. If provider changes, only adapter changes. This also makes tests easier because services mock your interface.

76. Use Decorator for adding caching.

Wrap an existing service with a caching decorator. The business service remains unchanged while the decorator checks cache before delegating.

77. Use Chain of Responsibility for validation.

Each validator checks one rule and passes to the next. This works for request validation, fraud checks, and approval flows. Return all errors or stop at first based on requirements.

78. Use Template Method for import jobs.

Base class defines steps: read, validate, transform, save, report. Subclasses customize format-specific steps. Use carefully because inheritance can become rigid.

79. Use Command pattern for undo.

Represent each user action as a command with execute and undo. Store command history. This works for editors, workflows, and admin tools.

80. Use Dependency Injection for testability.

Inject repositories, clients, and clocks through constructors. Tests can pass fakes. Avoid creating dependencies with new inside business logic.

Spring, REST, and APIs

81. Design an order REST API.

Use resource endpoints: create order, get order, update status, and list orders. Validate DTOs, keep business logic in services, return correct HTTP codes, and support pagination.

82. Secure a Spring Boot API.

Use HTTPS, authentication, authorization, validation, and safe error handling. Configure Spring Security with JWT or sessions. Put business authorization in service/method security too.

83. Validate request payloads.

Use Jakarta Bean Validation annotations on DTOs and @Valid in controllers. Convert validation errors to a consistent response using global exception handling.

84. Implement pagination and sorting.

Use offset pagination for simple cases and cursor pagination for large changing datasets. Always cap page size and sort on indexed columns.

85. Make create-order idempotent.

Require an idempotency key. Store key, request hash, status, and response. If the same key repeats, return the original response. Reject same key with different payload.

86. Add global API versioning.

Prefer backward-compatible changes. For breaking changes, use URL or header versioning. Keep old versions during deprecation and use contract tests.

87. Handle file download endpoint.

Stream the file response instead of loading it fully. Set content type, content length when known, and content disposition. Authorize before streaming.

88. Design API rate limiting.

For one app instance, token bucket works in memory. For many instances, use Redis, gateway, or load balancer. Return 429 and include retry information.

89. Implement health checks.

Liveness means process is alive. Readiness means it can receive traffic. Check critical dependencies carefully without overloading them. Expose build/version info for debugging.

90. Handle CORS safely.

Allow only trusted origins, methods, and headers. Do not use wildcard origins with credentials. Keep frontend and backend environments explicit.

Database and Persistence

91. Avoid Hibernate N+1 queries.

Detect with SQL logs. Fix using fetch joins, entity graphs, batch fetching, or DTO projections. Do not make everything eager because that can load huge graphs.

92. Use optimistic locking for concurrent updates.

Add @Version. If two users update the same row, the second update fails with conflict. Ask the user to reload or merge. Works best when conflicts are rare.

93. Handle connection pool exhaustion.

Check active, idle, pending, and timeout metrics. Fix leaks, slow queries, missing indexes, and long transactions. Do not hold DB connections while calling remote APIs.

94. Migrate schema safely.

Use Flyway or Liquibase. Prefer expand-migrate-contract: add nullable column, deploy code, backfill, then enforce constraint or remove old column later.

95. Design full-text search.

Use database indexes for simple filters and OpenSearch/Elasticsearch for relevance, fuzzy matching, and large text. Build denormalized search documents from events.

Microservices and Cloud

96. Split a monolith into microservices.

Split by business capability, not technical layer. Each service should own its data. Start with one low-risk boundary and use APIs/events to communicate. Mention operational cost.

97. Keep services consistent without distributed transactions.

Use saga pattern. Each service commits locally and publishes an event. If a later step fails, run compensating action. Make every step idempotent.

98. Publish reliable events.

Use transactional outbox: write business row and outbox row in one transaction. A relay publishes outbox rows to the broker and marks them sent.

99. Add distributed tracing.

Propagate trace id through HTTP and message headers. Use OpenTelemetry. Add trace id to logs. Sampling controls cost. Traces reveal slow service hops.

100. Design graceful shutdown in Kubernetes.

On SIGTERM, stop accepting new traffic, drain in-flight requests, pause consumers, close pools, and flush logs. Configure readiness and termination grace period.

Advanced Architecture Scenarios

101. Design notifications for email, SMS, and push.

Define NotificationChannel implementations. Select channels by user preference and business rule. Queue delivery, record status, retry transient provider errors, and use dead-letter handling.

102. Design an audit trail.

Create immutable audit events with actor, action, target, timestamp, trace id, and changed fields. Store with the transaction if legally required. Mask sensitive data and prevent edits.

103. Design multi-tenant SaaS support.

Choose shared database with tenant id, separate schema, or separate database. Include tenant id in queries and cache keys. Add tests to prevent cross-tenant leaks.

104. Schedule jobs without duplicate execution.

In one JVM, use ScheduledExecutorService. Across instances, use distributed locks with DB, Redis, Quartz, or scheduler. Jobs must be idempotent because crashes can repeat work.

105. Design a configurable rule engine.

Store rules as priority, condition, and action. Evaluate against a context. Validate rules before activation, version them for audit, and keep the rule language constrained so it cannot execute unsafe code.

No comments
Leave a Comment