Link for the 1st part : In this video, we will discuss about the interview questions and answers in the J.P Morgan Chase & Co company for Java backend developer profile. Q8. How do you design a rate limiter for an API? Ans: Designing a rate limiter for an API is critical to prevent abuse, ensure fair use, and protect backend resources. Below is a step-by-step guide to design a robust rate limiter Step 1: Understand the Requirements Rate limit criteria (e.g., 100 requests per minute) Scope: Per user Per IP Per API key Per endpoint Burst allowance: Can a client exceed the rate limit briefly? Penalty behavior: Should we block, delay, or throttle requests? Step 2: Choose a Rate Limiting Algorithm Algorithm Description Fixed Window Count requests in a fixed interval (e.g., per minute). Sliding Window More accurate than fixed, uses multiple time buckets. Token Bucket Tokens are added at a fixed rate; requests consume tokens. Allows bursts. Leaky Bucket Like Token Bucket but enforces a constant output rate. Step 3: Decide Where to Implement Algorithm Description Client-side: Not secure, can be bypassed. API Gateway (AWS API Gateway): Recommended for centralized enforcement. Backend application: Good for custom logic (Java, Node, Python). Distributed system: Use Redis or similar to share state across instances. Step 4: Store the Rate Limit State You need a fast, in-memory store like: Redis: Ideal for distributed systems. In-memory (for single instance apps only). Step 5: Implement the Logic Step 6: Respond Appropriately When rate limit is hit, respond with: HTTP/1.1 429 Too Many Requests Retry-After: 60 Include rate limit headers: X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1620000300 Step 7: Monitor and Tune Log rate limiting metrics. Use alerts for abuse patterns. Adjust limits based on usage and server capacity. Step 8: Optional Enhancements Dynamic limits (per user tier: free vs premium). Blacklist/whitelist certain IPs or keys. Token bucket implemented using Redis Lua scripts for atomicity in distributed systems. Use libraries: Java: Bucket4j, Resilience4j : express-rate-limit Redis: Use INCR and TTL for fixed window rate limiting Q9. How would you make a service highly available and scalable? Ans: High Availability (HA)A system is highly available if it is continuously operational (uptime ~ %+), even when some components fail. Goal: Minimize downtime Measured via uptime SLA (e.g., %) ScalabilityA system is scalable if it can handle increased load by adding more resources, without degrading performance. Types: Vertical Scaling: Increase resources on a single node Horizontal Scaling: Add more nodes System Design Strategies Load Balancing Stateless Service Architecture Redundancy Auto-Scaling Database Scaling & Availability Caching Asynchronous Processing Monitoring, Logging, Alerting Q10. Write Java code to Check if parentheses are balanced in an expression. Q11. Find the longest substring without repeating characters? Example:Input: "abcabcbb"Output: 3Explanation: The answer is "abc", with the length of 3. Q12. What is the difference between synchronized, ReentrantLock, and volatile? SynchronizedPurpose:Ensures mutual exclusion, i.e., only one thread at a time can execute a synchronized block or method for a given object/class. Characteristics: Automatically acquires and releases the monitor lock. Can be used on methods or code blocks. Simpler and less error-prone. Cannot interrupt a thread waiting for the lock. No way to try locking without waiting. ReentrantLockPurpose:Also provides mutual exclusion like synchronized, but offers more : Explicitly acquire (lock()) and release (unlock()) the lock. Can try to acquire lock with tryLock() or with a timeout. Can be interrupted while waiting for the lock. Supports fairness policy (first-come-first-served). Must be manually released, so it’s error-prone if not done correctly. volatilePurpose:Ensures visibility of changes to variables across threads. Does NOT provide atomicity or mutual exclusion. Characteristics: Tells JVM that threads should read the value from main memory, not from CPU cache. Best for flags or state checks. Cannot be used to make compound actions (e.g., incrementing a variable) atomic. When to Use What? Use synchronized: For simple mutual exclusion, when you don't need advanced features. Use ReentrantLock: When you need: try-lock interruptibility fairness Use volatile: When you only need to make sure a variable's latest value is visible to all threads, not atomicity.











