RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
EasySpring Boot#118 min readJul 11, 2026

What is IoC?

spring-boot
di
practice
ioc
learning
Practice modeTest yourself instead of reading straight through

Spring IoC is the reason your code can feel calm instead of tangled: you describe what you need, and the framework brings the pieces together.

Question: What is IoC?

Answer: IoC means Inversion of Control. In Spring Boot, it means your code does not directly create and manage all its dependencies; the Spring container does that for you. This makes classes smaller, easier to test, and easier to replace with different implementations.

Interview-Ready Answer: IoC, or Inversion of Control, is the idea that the framework controls object creation, wiring, and lifecycle instead of my code doing all of that with new. In Spring Boot, the IoC container, usually the ApplicationContext, creates beans, injects their dependencies, and manages them for me. The big benefit is loose coupling: I can swap implementations, test with mocks, and keep business code focused on business rules.

🧠 Memory Map
Memory map — visual summary of this topic

Core idea

In plain English, IoC means your code stops being the boss of every object it needs. A bean is just an object managed by Spring. Instead of writing new PaymentService() inside another class, you let Spring create that service and hand it to the class that needs it. That shift is important because the class no longer knows how the dependency is built; it only knows what it needs.

How Spring does it under the hood

  1. Spring Boot starts and scans your application classes, looking for bean definitions such as @Component, @Service, @Configuration, and @Bean.
  2. It builds a list of bean definitions first. A bean definition is the recipe for creating an object; the object itself may not exist yet.
  3. For most beans, Spring creates them at startup as singleton beans, which means one shared instance per application context by default.
  4. Spring resolves dependencies by type. If a constructor asks for OrderRepository, the container looks for a matching bean and injects it.
  5. If there is more than one candidate, Spring uses rules such as @Primary, @Qualifier, or the parameter name to choose the right one.
  6. After creation, Spring can run bean post-processors and wrap beans in proxies. A proxy is a wrapper object used for features like transactions, security, and caching.
  7. When the app shuts down, Spring can call destroy callbacks so resources such as file handles or network clients can be cleaned up properly.

Why this is useful

IoC gives you loose coupling, which means code pieces depend on abstractions instead of hard-coded classes. That makes testing much easier because you can replace real dependencies with mocks or fakes. It also improves configuration because you can change behavior by changing beans, profiles, or properties instead of rewriting business logic.

IoC vs DI vs Service Locator

People often mix these terms together, so here is the clean version: IoC is the broad principle, DI (Dependency Injection) is the most common way to implement it in Spring, and the Service Locator pattern is an older alternative where objects ask a registry for dependencies themselves.

ApproachWho creates depsMain benefitMain risk
Manual newYour codeSimple for tiny objectsTight coupling
Dependency InjectionSpring containerEasy to test and swapMisconfigured beans
Service LocatorRegistry lookupCentral lookup pointHidden dependencies

Performance and complexity

IoC has some startup cost because Spring scans classes, builds bean definitions, and creates many singletons before the app is ready. That cost is roughly proportional to the number of beans and the amount of classpath scanning, so larger apps take longer to start. At runtime, bean lookup is generally very fast because Spring keeps internal caches and maps, so dependency access is usually close to O(1) average time. The real trade-off is usually startup time versus cleaner architecture, not request-time speed.

Important edge cases

  • Circular dependency: two beans depend on each other. Spring may fail startup or need a proxy-based workaround, but the better fix is usually to redesign the dependency graph.
  • Multiple beans of the same type: without @Primary or @Qualifier, Spring cannot know which one to inject.
  • Prototype into singleton: a prototype bean is created every time it is requested, but if you inject it once into a singleton, you do not get a fresh instance on every use unless you ask for it lazily.
  • Field injection: it works, but constructor injection is clearer because required dependencies are visible and easier to test.

Memory model: think of Spring as the workshop manager. You bring the blueprint, and Spring builds, wires, and maintains the tools. Your classes become workers that know their job, not the entire factory layout.

Real-world story

Imagine a checkout service in an e-commerce system. It needs a payment gateway, an inventory client, and a fraud checker. If a developer directly creates those objects inside the service with new, the service becomes hard to test, hard to swap, and easy to leak resources like HTTP connections.

In one production incident, a team created a new payment client for every request instead of letting Spring manage a singleton client bean. During a flash sale, traffic jumped from 200 requests per second to 2,000, and the app started logging connection exhaustion errors such as Timeout waiting for connection from pool. Checkout latency rose from around 120 ms to several seconds, and users saw failed payments even though the business code itself looked fine.

With IoC, the payment client is a bean. Spring creates it once, injects it where needed, and can apply config, retries, or metrics consistently. Tests can swap the real gateway bean for a fake one, so checkout logic is verified without hitting the real payment provider.

What goes wrong when IoC is misunderstood: the code hides dependencies, creates too many objects, or ties itself to one concrete class. The symptoms are noisy logs, flaky tests, hard-coded URLs or credentials, and startup failures when one dependency is missing or duplicated.

Spring Boot
package com.example.iocdemo;

import java.time.Clock;
import java.time.Instant;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(IocDemoApplication.class, args);
    }

    @Bean
    Clock clock() {
        // Spring manages this shared dependency, so any bean that needs time can reuse the same source.
        return Clock.systemUTC();
    }

    @Bean
    GreetingService greetingService() {
        // The business code depends on the interface, not on a concrete class created with new.
        return name -> "Hello, " + name + "!";
    }

    @Bean
    CommandLineRunner demo(GreetingService greetingService,
                           Clock clock,
                           ObjectProvider<AuditService> auditServiceProvider) {
        return args -> {
            System.out.println("Greeting: " + greetingService.greet("Spring IoC"));
            System.out.println("Time: " + Instant.now(clock));

            // Edge case: this dependency is optional. If Spring cannot find a bean, the app still starts.
            AuditService auditService = auditServiceProvider.getIfAvailable();
            if (auditService == null) {
                System.out.println("No AuditService bean configured, so we skip auditing instead of failing startup.");
            } else {
                auditService.audit("Application started");
            }
        };
    }

    @Bean
    @ConditionalOnProperty(name = "app.audit.enabled", havingValue = "true")
    AuditService auditService() {
        return message -> System.out.println("[AUDIT] " + message);
    }

    interface GreetingService {
        String greet(String name);
    }

    interface AuditService {
        void audit(String message);
    }
}

Follow-up & Tricky Questions:

  • What is the difference between IoC and DI? IoC is the broader principle: control is inverted away from your code. DI is the common Spring technique used to achieve that inversion by passing dependencies into constructors, setters, or fields.
  • What is Spring IoC container? It is the part of Spring, mainly ApplicationContext, that reads bean definitions, creates objects, injects dependencies, and manages lifecycle callbacks.
  • Why is constructor injection preferred? It makes required dependencies explicit, supports immutability, and makes unit tests easy because you can create the class with mocks directly.
  • How does Spring choose between multiple beans of the same type? It uses @Primary, @Qualifier, and sometimes parameter names to resolve the correct bean.
  • What is a bean? A bean is any object that Spring creates and manages inside the container.
  • Is IoC only about object creation? No. It also covers wiring, lifecycle management, and often proxying for cross-cutting features like transactions.
  • Can IoC help with testing? Yes. You can replace real beans with mocks, stubs, or test configurations so business logic is tested in isolation.
  • Can you use IoC without Spring Boot? Yes. IoC is a general design idea. Spring Boot just gives you a very convenient container and auto-configuration on top of it.
  • Is @Autowired mandatory? No. With a single constructor, Spring Boot can inject dependencies automatically without the annotation, and that is often the cleanest style.
  • Is a Spring singleton the same as the Singleton design pattern? No. Spring singleton means one instance per application context, not necessarily one instance for the entire JVM.
  • Does every object in a Spring app need to be a bean? No. Small value objects and temporary helper objects can still be created with new when container management is unnecessary.

Tricky / gotcha questions:

  • Can a field annotated with @Autowired be private? Yes, Spring can inject it, but field injection is still less preferred because the dependency is hidden and harder to test.
  • If Spring manages the object, does that automatically mean IoC? Usually yes, because the container now controls creation and wiring. The key is that your code no longer decides everything itself.
  • Does IoC mean you never call methods on dependencies? No. You still call methods on injected dependencies; the control inversion is about ownership of creation and wiring, not about avoiding method calls.

Common Mistakes:

  • Mixing up IoC and DI. Correction: IoC is the principle; DI is the implementation style most Spring apps use.
  • Saying IoC only means @Autowired. Correction: IoC is broader than one annotation; it includes bean creation, wiring, and lifecycle.
  • Using field injection everywhere. Correction: prefer constructor injection so dependencies are visible and required inputs are explicit.
  • Thinking Spring singleton means one object for the whole JVM. Correction: it means one object per Spring application context.

Memory Hook: You write the recipe, Spring runs the kitchen. You describe the ingredients and the dish; Spring decides when to mix, cook, and serve the objects.

Cheat Sheet:

  • IoC = framework controls object creation and wiring.
  • DI = the usual way Spring achieves IoC.
  • Spring beans live in the ApplicationContext.
  • Constructor injection is the safest default.
  • Use @Primary or @Qualifier when multiple beans match.
  • IoC improves testability, flexibility, and lifecycle management.

Practice Tasks:

  • Rewrite one class that uses new inside it so the dependency is injected through the constructor.
  • Create two implementations of the same interface and switch between them with @Primary and @Qualifier.
  • Write a small test that replaces a real Spring bean with a mock and verify the business logic still works.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

package com.example.iocdemo; import java.time.Clock; import java.time.Instant; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @SpringBootApplication public class IocDemoApplication { public static void main(String[] args) { SpringApplication.run(IocDemoApplication.class, args); } @Bean Clock clock() { // Spring manages this shared dependency, so any bean that needs time can reuse the same source. return Clock.systemUTC(); } @Bean GreetingService greetingService() { // The business code depends on the interface, not on a concrete class created with new. return name -> "Hello, " + name + "!"; } @Bean CommandLineRunner demo(GreetingService greetingService, Clock clock, ObjectProvider<AuditService> auditServiceProvider) { return args -> { System.out.println("Greeting: " + greetingService.greet("Spring IoC")); System.out.println("Time: " + Instant.now(clock)); // Edge case: this dependency is optional. If Spring cannot find a bean, the app still starts. AuditService auditService = auditServiceProvider.getIfAvailable(); if (auditService == null) { System.out.println("No AuditService bean configured, so we skip auditing instead of failing startup."); } else { auditService.audit("Application started"); } }; } @Bean @ConditionalOnProperty(name = "app.audit.enabled", havingValue = "true") AuditService auditService() { return message -> System.out.println("[AUDIT] " + message); } interface GreetingService { String greet(String name); } interface AuditService { void audit(String message); } }