InterviewLegend

Java and Spring Internals

Alex Chen38 min readUpdated 116 real interview reports
In this lesson
  • Explain how a HashMap stores and finds an entry, and what breaks when keys misbehave
  • Hold the concurrency conversation at depth, including the async fan-out question Mastercard asks by name
  • Explain JVM memory and generational garbage collection, and where OutOfMemoryError and Metaspace come from
  • Explain the Spring machinery behind the annotations, from injection to proxies to the path of a REST request

The data-structure problem in a Mastercard backend round is the opening move. This lesson is the gate behind it. Once you have the algorithm working, an engineer who ships Java payment services for a living spends the rest of the room asking what the platform is doing underneath your code. The same questions come back loop after loop. This is the study resource for that surface. It takes the recurring questions and gives you the model answer for each, in the shape the interviewer is listening for. Name the thing, explain the mechanism, then reason about what breaks when it fails.

The Mastercard angle
Prepare this as a first-class subject, because Mastercard runs a Java and Spring Boot backend and grades the platform as something you can be examined on. The people scoring you build and operate transaction services in this exact stack. So the questions push past "what does this annotation do" into what proxy the container generates, when the garbage collector pauses your thread, and why a HashMap with a broken key silently loses entries. If your resume says Java, expect to defend it at the level of the runtime and the framework. This is where a candidate who "uses Spring" and a candidate who understands Spring separate, and on a backend loop the second one gets the offer.

The material falls into three bodies of knowledge, and this lesson walks them in order. First the Java platform itself, collections and concurrency and the memory model, because that is the language the whole interview is conducted in and the deepest well of follow-ups. Then the JVM, how memory is laid out and how garbage collection reclaims it, which is where "why is my service slow" questions live. Then Spring core, inversion of control and the bean lifecycle and the request path, which is the framework your day job runs on. Every answer below follows the same arc, and drilling that arc is as important as the facts inside it, because the round is testing whether you reason about the platform or merely recite it.

Java collections and concurrency

HashMap internals

The single most reliable Java question is "how does a HashMap work internally," and the interviewer wants the machinery under the interface. A HashMap is an array of buckets. When you put a key, it calls the key's hashCode, spreads those bits so a weak hash does not cluster, and reduces the result to a bucket index. If two keys land in the same bucket, that is a collision, and the entries chain together in that bucket. Retrieval reverses the walk: hash the key to the bucket, then walk the entries in that bucket comparing with equals until one matches. That is why both methods matter, hashCode chooses the bucket and equals settles ties inside it, and getting either wrong is exactly the bug the follow-up probes.

java
// put(key, value), conceptually:
// 1. h = spread(key.hashCode()) // mix the bits
// 2. i = h & (table.length - 1) // bucket index, length is a power of two
// 3. walk bucket i; if a node's key .equals(key), overwrite; else append
//
// get(key):
// 1. same hash, same bucket index i
// 2. walk bucket i comparing with .equals until a match, else null
// Two facts the interviewer is fishing for:
// - the index step is a bitwise AND, which is why capacity is always a power of two
// - a bucket that grows past a threshold converts from a linked list to a
// balanced tree so worst-case lookup in that bucket is logarithmic, not linear

Two numbers are worth stating because they show you have read the source rather than a blog. The default initial capacity is 16 and the default load factor is 0.75. Once the map is three-quarters full it resizes, doubling the bucket array and rehashing every entry into the larger table. That is expensive, and you avoid it by sizing the map up front when you know the count. The second is treeification. When a single bucket accumulates 8 entries and the table is large enough, that bucket converts from a linked list into a balanced tree. A pathological collision run then degrades to logarithmic lookup instead of linear. Each number answers a "what happens when" follow-up. Resize answers "what does a growing map cost," and treeify answers "what stops a bad hashCode from making get O of n."

Have the equals-and-hashCode contract ready as a single rule, because it is the most common trap in the whole Java battery. The contract is: if two objects are equal by equals, they must return the same hashCode. Break it and a HashMap misbehaves silently. You store an entry under a key, then look it up with an equal key whose hashCode differs. The lookup hashes to a different bucket, finds nothing, and returns null even though the entry is right there. The reverse is legal, two unequal objects may share a hashCode, that is just a collision the equals check resolves. State the practical consequences. Override both methods together or neither, build them from the same fields, and never use a mutable field in the calculation. Mutating a key after it is in the map moves its correct bucket out from under it and strands the entry.

HashMap sits inside a small family the interviewer expects you to place. A HashMap gives no ordering guarantee and allows one null key. A LinkedHashMap preserves insertion order, or access order if you ask, by threading a doubly linked list through the entries, which is the standard way to build an LRU cache. A TreeMap keeps keys sorted by their natural order or a comparator, and gives you logarithmic operations plus range queries. The cost is that log factor versus the average constant time of a hash map. HashMap is not thread-safe. The wrong fixes are a HashMap guarded by external synchronization or the legacy Hashtable that locks the whole map on every call, and they set up the next question.

ConcurrentHashMap and thread-safe collections

The natural escalation is "so how do you share a map across threads," and the answer the interviewer wants is ConcurrentHashMap, along with why it beat the older options. A synchronized map or the legacy Hashtable guards the entire structure with a single lock. Two threads touching two unrelated keys still serialize behind that one lock, and under load the map becomes the bottleneck. ConcurrentHashMap fixes this by locking at a much finer grain. Reads are effectively lock-free, and writes lock only the individual bucket being modified rather than the whole table, so threads working on different buckets proceed in parallel. That is the entire pitch: concurrency without a global lock, which is what a transaction service under real traffic needs.

Two behaviors separate a real answer from a memorized one. First, ConcurrentHashMap deliberately disallows null keys and values. In a concurrent map a null returned from get is ambiguous, since it cannot distinguish "absent" from "present but null," so the design forbids null to remove the ambiguity. Second, its iterators are weakly consistent instead of fail-fast. You can iterate while other threads mutate the map without a ConcurrentModificationException. The iterator reflects the state at some point during the traversal, with no guarantee it sees every concurrent change. Contrast that with the fail-fast iterator on a plain HashMap, which throws the moment it detects a structural change. That shows you know why the two classes' guarantees differ, beyond which one to reach for.

java
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
// WRONG under concurrency: get, add, put is a read-modify-write race.
// Two threads can both read 5, both write 6, and one increment is lost.
Integer n = counts.get(key);
counts.put(key, (n == null ? 0 : n) + 1);
// RIGHT: a single atomic operation the map performs under the bucket lock.
counts.merge(key, 1, Integer::sum);
// or
counts.compute(key, (k, v) -> v == null ? 1 : v + 1);
// The lesson: thread-safe on each call does not make a SEQUENCE of calls
// atomic. Use the built-in atomic combinators for compound updates.

The senior tell here is naming the gap between "each method is thread-safe" and "my logic is thread-safe." A ConcurrentHashMap guarantees every individual put, get, and remove is atomic and correctly synchronized. It does not make your read-modify-write sequence atomic, so a get followed by a put can still lose updates when two threads interleave between the two calls. The fix is to push the whole compound operation into one call the map executes atomically, merge, compute, computeIfAbsent, or putIfAbsent. State that boundary explicitly and you show you understand concurrency as a property of operations over time instead of a label on a class. The round is checking exactly that distinction.

synchronized versus Lock, and thread pools

The concurrency primitives come next, and the first contrast is synchronized versus the explicit Lock. The synchronized keyword is the built-in intrinsic lock, and every object has one. It is simple and safe because the lock is released automatically when the block exits, even on an exception, so you cannot forget to release it. The Lock interface, usually ReentrantLock, was built to fix its limits. A synchronized block cannot time out, cannot be interrupted while waiting, and cannot try to acquire and give up if the lock is busy. A ReentrantLock offers tryLock with a timeout, interruptible acquisition, and optional fairness, at the cost that you must call unlock() in a finally block yourself, because nothing does it for you. State the rule of thumb, reach for synchronized by default for its simplicity, and step up to an explicit Lock when you specifically need a timeout, interruptibility, or fairness.

synchronized (intrinsic lock)

  • Built into every object; simplest correct mutual exclusion
  • Auto-released on block exit and on exception, so you cannot leak the lock
  • Cannot time out, cannot be interrupted while blocked, no try-and-give-up
  • The right default when you just need one thread in the section at a time

ReentrantLock (explicit Lock)

  • tryLock with a timeout, interruptible acquisition, optional fairness
  • You MUST call unlock() in a finally block yourself, or the lock leaks
  • Supports multiple condition variables and a separate read/write lock variant
  • Step up to it only when you need a capability synchronized lacks

Also keep volatile straight from synchronized, because the round often pairs them. The volatile keyword guarantees visibility: a write by one thread is seen by the next read on another thread. It also prevents the compiler from caching the value in a register. It does not provide atomicity, so a volatile counter still loses increments, because the increment is a read-modify-write and takes more than one action. Use volatile for a simple flag one thread sets and another polls. Reach for an AtomicInteger and its compare-and-swap operations for a lock-free counter, and use a lock when you must make several fields consistent together. Naming that ladder, volatile for visibility of one field, atomics for a single variable's read-modify-write, a lock for a multi-field invariant, is a crisp senior answer.

Thread pools are the last concurrency topic and the one closest to production. Creating a thread is expensive, and an unbounded number of threads will exhaust memory and thrash the scheduler. A pool keeps a fixed set of worker threads alive and feeds them a queue of tasks. In Java that is an ExecutorService, which you submit Runnable or Callable tasks to and which hands back a Future you can await for the result. Say the difference the interviewer wants. A Runnable returns nothing and cannot throw a checked exception; a Callable returns a value and can. Submitting a Callable is how you get a Future to wait on. Prefer a pool over spawning raw threads because it bounds resource use, reuses threads, and gives you a clean place to handle rejection when the queue is full.

A question Mastercard asks by name is "call two APIs in parallel, a third synchronously, then wait for the first two," so have the shape ready. The clean answer is CompletableFuture. Kick off the two independent calls with supplyAsync so they run on the pool concurrently, and do the synchronous call inline on the current thread. Then join the two futures to block until both finish, and combine the results. The reason this beats raw threads is that you never manage thread lifecycle or shared result variables by hand, the framework composes the futures and propagates exceptions for you. If the interviewer insists on raw threads, start two Threads, run the third call inline, then call join on both. Lead with CompletableFuture, though, because it is what the platform gives you.
java
ExecutorService pool = Executors.newFixedThreadPool(4);
// APIs 1 and 2 run in parallel on the pool.
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> callApi1(), pool);
CompletableFuture<String> b = CompletableFuture.supplyAsync(() -> callApi2(), pool);
// API 3 runs synchronously on this thread while 1 and 2 are in flight.
String c = callApi3();
// Now block until both async calls complete, then combine.
CompletableFuture.allOf(a, b).join();
String result = a.join() + b.join() + c;
pool.shutdown();
// Runnable vs Callable: supplyAsync takes a Supplier (returns a value);
// a Runnable returns nothing, a Callable returns a value and may throw checked.

JVM memory and garbage collection

The JVM grilling is lighter than the collections one but it is where "why did the service pause" and "why did it run out of memory" questions come from, so hold the model. Answer each the same way, sketch where the memory lives, then explain what the collector does to it, then name the failure the interviewer is really probing. Lead with the framing that Java manages memory for you, so you never free an object. The garbage collector reclaims anything no longer reachable from a live root. The art is arranging for that reclamation to be cheap and rarely disruptive.

The runtime memory areas

Start with stack versus heap, because it is the base question and every follow-up hangs off it. Each thread has its own stack, holding one frame per method call with that call's local variables and the object references it holds. A frame is pushed on call and popped on return, which is automatic and cheap. The heap is shared by all threads and holds every object, the actual instances your references point at. So a local variable that is an object lives as a reference on the stack pointing to an instance on the heap. That is why passing a reference lets two methods see the same object. State the two matching failures: a stack overflow is unbounded recursion filling a thread's stack, and an OutOfMemoryError is the heap exhausted, usually a leak where objects stay reachable and so uncollectible.

Then the piece that catches people, Metaspace and the old PermGen, because it is a direct Mastercard question. Class metadata, the definition of each loaded class, does not live on the object heap. Before Java 8 it lived in a fixed-size region called the permanent generation, PermGen, which was easy to exhaust with many loaded classes and threw its own OutOfMemoryError. Java 8 replaced PermGen with Metaspace, which stores the same class metadata in native memory that grows on demand rather than in a fixed slice of the heap. The interviewer wants you to know that the migration happened and why: PermGen's fixed sizing was a chronic operational headache. A class-loading leak now shows up as Metaspace growth instead of a PermGen error.

How generational garbage collection works

Garbage collection is the flagship JVM question, so give it a crisp mechanism. The collector's job is to find objects still reachable from a set of roots, the live thread stacks, static fields, and similar, and reclaim everything else. Every modern collector exploits the weak generational hypothesis, which says most objects die young. A huge share of allocations become garbage almost immediately, while the few that survive tend to live a long time. So the heap is split into a young generation and an old generation. New objects are allocated in the young generation, which is collected often and quickly by a minor collection that copies the few survivors out and wipes the rest wholesale. Objects that survive enough minor collections are promoted, tenured, into the old generation, which fills slowly and is collected by a rarer, more expensive major collection.

The reason this design matters to the interviewer is pause time. A minor collection touches only the small young generation and is fast. A full collection of the old generation can pause application threads noticeably. That stop-the-world pause is what you are trying to avoid in a latency-sensitive payment service. That is why modern collectors like G1 and the newer low-pause collectors work in small increments and target a pause budget rather than collecting the whole heap at once. You do not need to configure a collector to answer well. Explain the generational split and why minor collections are cheap and major ones are not. Then say that tuning is about keeping objects dying young so they never reach the expensive old generation.

The mature framing that lands a GC answer is that garbage collection prevents leaks in the C sense but does not prevent leaks in the Java sense. You never dangle a freed pointer or double-free, because the collector owns reclamation. But you can still leak by keeping objects reachable that you no longer need. Think of a static collection that only ever grows, a cache with no eviction, or a listener you registered and never removed. The collector correctly refuses to reclaim them, and the heap climbs to an OutOfMemoryError. A strong answer names a Java memory leak as unwanted reachability. The fix is dropping the reference, bounding the cache, or deregistering the listener. Calling System.gc fixes nothing, since it is only a suggestion the JVM is free to ignore.

Spring core: IoC, beans, and the request path

The Spring round is heavily weighted for backend roles, and it is graded on whether you understand the framework as machinery or only as a set of annotations you paste. The whole framework rests on one idea, inversion of control, and once that clicks the bean lifecycle, dependency injection, transactions, and the request path all fall out of it. Answer each as a mechanism: what object does the container create, when does it create it, and what does it wire into what. That is the register the deep Spring interviewer is listening in.

IoC, dependency injection, and the bean lifecycle

Inversion of control means your code does not construct its own collaborators, the framework does, and hands them to you. Instead of a service calling new on the repository it depends on, the Spring container creates the repository, creates the service, and injects the first into the second. Dependency injection is the mechanism that performs that handoff. Your class declares what it needs and never how to build it. That keeps it loosely coupled and trivial to test, because you can inject a mock in place of the real collaborator. The container that does all this is the ApplicationContext, and the objects it manages are called beans.

Name the three forms of injection and state which is preferred, because that is a direct question. Constructor injection passes dependencies as constructor arguments, and it is the recommended form. It lets you mark the fields final, guarantees the object is fully initialized the moment it exists, and makes a missing dependency a construction-time failure. Setter injection uses a setter after construction, which suits genuinely optional dependencies. Field injection annotates the field directly. It is the most concise form and the most common in tutorials, but the least testable, because you cannot set the field without the container or reflection. Move away from it. Also have the stereotypes straight: @Component is the generic bean, and @Service, @Repository, and @Controller are @Component specializations that document intent and, for @Repository, add persistence-exception translation.

java
@Service
public class PaymentService {
private final PaymentRepository repo; // final: set once, never null
private final FraudClient fraud;
// No @Autowired needed on a single constructor in modern Spring.
// The container sees the two parameters and injects the matching beans.
public PaymentService(PaymentRepository repo, FraudClient fraud) {
this.repo = repo;
this.fraud = fraud;
}
}
// Why constructor over field injection:
// - the object is fully built and valid the instant it exists
// - the fields are final, so they can't be reassigned or left null
// - you can construct it in a unit test with plain 'new', passing mocks,
// without starting a Spring context at all

The bean lifecycle is the next layer, and the interviewer wants the ordered story. On startup the container reads the bean definitions, instantiates each bean, injects its dependencies, calls any configured initialization callback such as a method annotated with @PostConstruct, and then the bean is ready to serve. On shutdown it calls the destruction callback, such as @PreDestroy, so the bean can release resources. Scope changes when this happens. The default scope is singleton: one shared instance per container, created eagerly at startup. A prototype-scoped bean is created fresh on every request for it, and the container does not manage its destruction. Web scopes like request and session tie a bean's life to an HTTP request or a user session. Knowing that a singleton is one shared instance, and therefore must be stateless or thread-safe, is a common and important follow-up.

When more than one bean could satisfy an injection point, the container needs a tiebreaker. The round asks how you resolve the ambiguity. Marking one candidate with @Primary makes it the default choice when several match. Adding @Qualifier names the specific bean you want at the injection point, overriding the default. Both exist because dependency injection is by type first, and the moment two beans share a type Spring cannot guess which you mean, so it fails fast at startup unless you disambiguate. Being able to say that the resolution is type-then-qualifier, and that an unresolved ambiguity is a startup failure rather than a runtime surprise, shows you understand how the container wires the graph.

@Transactional and AOP under the hood

How @Transactional works under the hood is the signature deep-Spring question, and the answer is a proxy. When Spring sees a bean with transactional methods, it does not inject your class directly, it injects a generated proxy that wraps it. A call to a transactional method hits the proxy first, which opens a database transaction and calls through to your real method. The proxy commits if the method returns normally, or rolls back if it throws a runtime exception. This is aspect-oriented programming, AOP, in action: the transaction management is a cross-cutting concern woven around your method by the proxy rather than written inside it. The same proxy mechanism powers @Async, method-level security, and caching, so understanding it once explains a whole family of annotations.

The famous gotcha, and a direct Mastercard follow-up, is that a self-invoked @Transactional method does not start a transaction. Because the behavior lives in the proxy, it only fires when the call arrives through the proxy from outside the bean. If a method inside the same bean calls another method on this, the call goes straight to the real object and bypasses the proxy entirely. The transactional annotation on the inner method is silently ignored. The same mechanism explains a second trap: by default only unchecked, runtime exceptions trigger a rollback, a checked exception commits unless you configure rollbackFor. And it explains nested-transaction behavior. The propagation setting governs whether an inner method joins the caller's transaction or suspends it to open its own. REQUIRED joins, and REQUIRES_NEW suspends the caller's and starts a fresh one.
java
@Service
public class OrderService {
public void placeOrder(Order o) {
save(o); // SELF-CALL through 'this' -> bypasses the proxy,
// so @Transactional on save() does NOTHING here
}
@Transactional
public void save(Order o) { /* ... */ }
}
// The fix: call save() from OUTSIDE the bean (e.g. a caller injects
// OrderService and calls save() directly, so the call goes through the
// proxy), or inject a self-reference, or split into two beans.
//
// Mechanism to say out loud: the annotation is honored by the PROXY that
// wraps the bean, and 'this.save()' never touches that proxy.

A worthwhile aside is how the proxy is built, because a sharp interviewer probes it. Spring uses one of two strategies. When your bean implements an interface, a JDK dynamic proxy implements that same interface. When it does not, CGLIB generates a subclass of your bean at runtime and overrides its methods. Both rely on reflection to intercept the call and run the surrounding advice. This is also why a proxied method must be public and why final methods and final classes cannot be proxied by the subclassing strategy, another edge the deep round enjoys. You do not need to implement a proxy. Say the words dynamic proxy, CGLIB subclass, and reflection, and explain that the advice runs before and after your method around the intercepted call.

How a REST request flows through Spring

The last core question is to trace a REST request end to end, and the interviewer wants the whole path named in order. The request arrives at the embedded servlet container, Tomcat by default in Spring Boot, which hands it to the single front controller, the DispatcherServlet, that fronts every request. The DispatcherServlet consults a handler mapping to find which controller method matches the URL and HTTP verb. It runs any registered filters and interceptors on the way in, binds and validates the request body and path variables into your method arguments, and invokes your controller method. Your method returns an object, a message converter, usually the Jackson one, serializes it to JSON, and the response travels back out through the same chain. Naming DispatcherServlet as the front controller is the keystone of this answer.

java
@RestController // @Controller + @ResponseBody: return values
@RequestMapping("/payments") // become the JSON response body directly
public class PaymentController {
@PostMapping // maps HTTP POST /payments to this method
public PaymentResponse create(@Valid @RequestBody PaymentRequest req) {
// @RequestBody: Jackson deserializes the JSON body into PaymentRequest
// @Valid: bean-validation runs before the method body executes
return service.create(req); // returned object is serialized back to JSON
}
}
// PUT vs POST, a common quick follow-up: POST creates and is not idempotent
// (two calls make two resources); PUT replaces at a known URI and IS
// idempotent (two identical calls leave the same single state).

Two neighboring topics ride along with the request path because they live on the same chain. Global exception handling uses @ControllerAdvice and @ExceptionHandler. That class centralizes how exceptions thrown by any controller become clean HTTP error responses, so you do not scatter try-catch through every method. And Spring Security inserts a chain of servlet filters ahead of the DispatcherServlet. An authentication filter validates the credential (a JWT for a stateless API) and establishes the security context. Then an authorization check, often @PreAuthorize on the method, decides whether this principal may proceed, all before your controller runs. You do not need the full filter list. Say that security is a filter chain in front of the dispatcher, and that the same proxy machinery as transactions enforces method-level rules.

How these surface as coding follow-ups

The reason this material is worth rehearsing to reflex is that it rarely arrives as a standalone quiz. It arrives bolted onto the coding problem you just solved, as the interviewer pulls on a thread in your solution to see how deep the platform knowledge goes. Solve a problem with a HashMap and the follow-up is "what if two threads hit this, and what does your key's hashCode need to guarantee." Write a loop that accumulates results and the follow-up is "how would you parallelize this," which routes straight to a thread pool or CompletableFuture. Mention that your service writes to a database and the follow-up is "how does the transaction get committed, and what happens if this method calls another one in the same class." The coding problem is the doorway, the platform question is the room.

The Mastercard angle
Rehearse the pivot from your solution into the internals, because that transition is where a Mastercard backend loop is won. When you finish coding, do not go quiet and wait, narrate the platform choices you made, the collection you picked and why, where a concurrency issue would appear, how you would make it thread-safe. That hands the interviewer the exact threads they were going to pull anyway and shows you think about production as well as the puzzle. The candidates who clear the deep round treat the algorithm as the setup and the "how does this actually run" conversation as the main event. They answer each follow-up with the definition first, then the mechanism, then the failure mode. Every question in this lesson rewards that same arc.

Every question in this battery rewards the same answer shape, so drill the shape along with the facts. Lead with a one-sentence definition, give the mechanism underneath it, then reason about the failure or tradeoff the interviewer is really probing. A HashMap finds an entry by hashCode to the bucket then equals inside it, and a broken contract loses the entry silently. ConcurrentHashMap locks per bucket, but a get-then-put sequence is still a race. Generational GC is cheap because most objects die young. A Java leak is unwanted reachability: objects you still reference but no longer need. @Transactional is a proxy, which is why a self-call bypasses it. A REST request flows through the DispatcherServlet front controller. Answer in that arc and you sound like an engineer who operates this stack. On a Java payments loop, that wins the round.

Key Takeaways

  • ✓Mastercard grades the Java and Spring platform as an examinable subject stacked on top of the algorithm, so prepare it as the center of a backend loop. Answer each question definition first, then mechanism, then the failure mode the interviewer is fishing for.
  • ✓On collections: a HashMap finds an entry by hashCode to a bucket then equals inside it, resizes at load factor 0.75 and treeifies a hot bucket; the equals-and-hashCode contract is the classic trap. ConcurrentHashMap locks per bucket and forbids nulls, but each method being atomic does not make a get-then-put sequence atomic, so use merge or compute.
  • ✓On concurrency, synchronized is the simple auto-released default. An explicit ReentrantLock buys timeout, interruptibility, and fairness at the cost of a manual unlock() in a finally block. A volatile field gives visibility without atomicity. To run work in parallel and await it, use a thread pool with an ExecutorService, or CompletableFuture for fan-out.
  • ✓On the JVM, the stack holds frames and references, the heap holds objects, and class metadata moved from PermGen to native Metaspace in Java 8. Generational GC is cheap because most objects die young; minor collections are fast and major ones pause. A Java memory leak is unwanted reachability that no collector will reclaim.
  • ✓On Spring, inversion of control means the container builds and injects your collaborators. Constructor injection is preferred, and beans are singletons by default. Transactions and security run through a generated proxy, which is why a self-invoked transactional method does nothing. A REST request flows through the DispatcherServlet front controller to your controller and back out as JSON.
Had your Mastercard interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Mastercard interview guide

Know what Mastercard asks before you walk in.

10 coding · 2 system design · 15 behavioral questions

Get Premium$49 a year · 30-day money-back guarantee
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.