Skip to main content

πŸ’‘ SOLID Principles: Why Interviewers Love Them & How They Can Make You a Better Developer!

Why is every microservices interview like a detective asking: ‘Do you know SOLID principles?’ 😩

Let’s be honest — lots of developers (especially freshers) get confused or forget them.
Haha… even I was one of them! πŸ˜…
And hey — I’m not the G.O.D (Guru Of Design) to remember everything every time! 🀷‍♂️πŸ˜‚
But once I started imagining LinkedIn’s real-life features — job posts, messaging, news feeds —
SOLID became unforgettable 🎯
And now, it's stuck in my brain… just like that one annoying ad you can’t skip. 😜


What Are SOLID Principles?

The five commandments of Object-Oriented Design, created by the software guru Robert C. Martin (Uncle Bob).
They help us write:

🧼 Clean
πŸ”§ Maintainable
πŸ”„ Flexible
🧱 Extensible
πŸ“¦ Reusable
code.


πŸ”‘ Why SOLID Matters in Microservices Interviews?

Because good code is like a good city — modular, scalable, maintainable.

Interviewers ask this because:

  • They want to know if you write code that grows well 🌱

  • If you design services that can be easily changed without breaking things πŸ’₯

  • If your code respects others — separation of concerns, low coupling, high cohesion 🀝

Now, let’s crack each letter of SOLID using something we all know: LinkedIn!


🧍‍♂️ S – Single Responsibility Principle (SRP)

“One class should do one thing — and do it well.”

πŸ‘” LinkedIn Analogy:

A JobPostingService should only post jobs — not send notifications or do analytics.

❌ What Goes Wrong If Ignored?

  • Changes in unrelated logic force you to touch this class

  • Difficult to test

  • Spaghetti monster grows πŸ‘Ύ

✅ Code Example:

// ❌ Bad class JobService { void postJob(Job job) {...} void sendNotification(User user) {...} void logAnalytics(Job job) {...} } // ✅ Good class JobPostingService { void postJob(Job job) {...} } class NotificationService { void sendNotification(User user) {...} } class AnalyticsService { void logJobView(Job job) {...} }

🧠 Dumb Question:

Q: But isn’t splitting everything a waste of classes?
A: No. It gives you superpowers when testing and changing stuff later.


πŸ‘‘ O – Open/Closed Principle (OCP)

“Software should be open for extension, but closed for modification.”

πŸ“¬ LinkedIn Analogy:

Imagine new message formats (text, GIF, poll) being added to MessagingService
— without changing the core class.

πŸ‘Ž BAD Example – OCP Violation
Let’s say you don’t follow the Open/Closed Principle and try to handle all message types directly inside MessagingService like this:


class MessagingService { void send(Message msg) { if (msg.getType().equals("TEXT")) { System.out.println("Sending text: " + msg.getContent()); } else if (msg.getType().equals("GIF")) { System.out.println("Sending GIF: " + msg.getUrl()); } else if (msg.getType().equals("POLL")) { System.out.println("Sending poll: " + msg.getQuestion()); } // ...and more if-else for every new type πŸ₯² } }

😩 What's wrong here?

  • Every time a new message type comes (e.g., voice, video, event invite)
    πŸ‘‰ You have to edit the MessagingService class
    πŸ‘‰ Risky changes, breaks existing functionality
    πŸ‘‰ Fails OCP: you're modifying instead of extending
    πŸ‘‰ And guess what? This class becomes a "God class" that does too much.

GOOD Example – OCP Followed (Strategy Pattern)


interface MessageFormatter { String format(Message msg); } class TextFormatter implements MessageFormatter { public String format(Message msg) { return "Text: " + msg.getContent(); } } class GifFormatter implements MessageFormatter { public String format(Message msg) { return "GIF: " + msg.getUrl(); } } class MessagingService { void send(Message msg, MessageFormatter formatter) { System.out.println(formatter.format(msg)); } }

πŸŽ‰ Now, if LinkedIn adds a new message format tomorrow...
Just create a new formatter class — no need to touch MessagingService at all!
Extend ➕, don’t modify ✂️


❌ If You Don’t Follow It

You keep editing MessagingService for every new feature = Risky 🚨


🧬 L – Liskov Substitution Principle (LSP)

"Subclasses must be usable in place of their parent classes without breaking the behavior."

🧾 LinkedIn Analogy:

Think of User and PremiumUser in LinkedIn.


If some part of the app expects a basic User, you should be able to pass a PremiumUser and it should still behave predictably.

No cheating. No surprise crashes. No broken promises.


πŸ‘‘ ❌ Bad Example (LSP Violation):


interface JobPostAccess { void accessFreeJobs(); } class PremiumUser implements JobPostAccess { public void accessFreeJobs() { throw new UnsupportedOperationException(); // ❌ Wait, what?! } }

Caller Code:

public void serveUser(JobPostAccess user) { user.accessFreeJobs(); // πŸ’₯ Boom! Crashed during runtime. } serveUser(new PremiumUser());

🧨 This is where things go south...

You're pretending that PremiumUser supports accessFreeJobs(), but when someone calls it — you throw an exception. This violates LSP because it breaks the contract promised by the interface.

Imagine LinkedIn showing the “Free Job Posts” button, and when a Premium user clicks it — they get slapped with “Feature not supported” 😫


✅ Good Example (LSP Compliant):

We restructure responsibilities honestly.


interface FreeJobAccess { void accessFreeJobs(); } interface PremiumJobAccess extends FreeJobAccess { void accessPremiumJobs(); }

class FreeUser implements FreeJobAccess { public void accessFreeJobs() { System.out.println("Viewing free jobs"); } } class PremiumUser implements PremiumJobAccess { public void accessFreeJobs() { System.out.println("Viewing free jobs"); } public void accessPremiumJobs() { System.out.println("Viewing premium jobs"); } }

Caller Code (Now safe and happy πŸ˜‡):


public void serveUser(FreeJobAccess user) { user.accessFreeJobs(); // Works for both FreeUser and PremiumUser }


🎯 What's the Real Lesson Here?

  • If your class implements an interface or extends a class, it is making a promise.

  • If you throw UnsupportedOperationException, you're breaking that promise.

  • This leads to confusing behavior, bugs, and angry developers (and users).


πŸ“¦ Real-Life Takeaway:

LSP is all about honesty in code.

Don’t say “I support this feature” and then whisper “but actually I don’t” when someone uses it. πŸ™ˆ


🧠 Interview Insight:
LSP violations usually show up in questions like:

“What happens if a subclass doesn't support a method defined in the parent?”
Or they give a code snippet with UnsupportedOperationException and ask:
“Is this okay?”

You now know: Nope, it’s not! 🚫 


🧲 I – Interface Segregation Principle (ISP)

"Don’t force classes to depend on methods they don’t use."

πŸ“¦ LinkedIn Analogy:

Let’s say you have a ContentModerator and a JobAdmin.
Would you make both of them implement postJob() and moderateContent() just because they’re part of “admin staff”?

That’s like asking a security guard to post job openings just because they’re in the office. 🀦‍♂️

❌ Bad Example (Interface Too Fat):


interface AdminActions { void postJob(); void moderateContent(); } class ContentModerator implements AdminActions { public void postJob() { throw new UnsupportedOperationException(); // 😩 Why am I forced? } public void moderateContent() { System.out.println("Flagging abusive posts."); } }

🚨 This breaks ISP. You're forcing ContentModerator to implement something it doesn’t care about. It’s not their job!



✅ Good Example (Interface Segregated Properly):


interface JobPosting { void postJob(); } interface Moderation { void moderateContent(); }

class ContentModerator implements Moderation { public void moderateContent() { System.out.println("Flagging fake profiles."); } } class JobAdmin implements JobPosting, Moderation { public void postJob() { System.out.println("Posting job: Java Developer πŸ”₯"); } public void moderateContent() { System.out.println("Removing scam job listings."); } }

Now, each class only implements what it truly needs. Clean, focused, and respectful of everyone’s job role 🧹


🧠 Interview Insight:

ISP violations show up as:
“You have 1 interface with 10 methods, but 4 of your classes only use 2-3 each… What would you do?”
πŸ‘‰ The correct answer is: Split it!
Design granular interfaces tailored to each responsibility.

 

πŸ§™‍♂️ Real-Life Analogy:

Don’t design interfaces like buffet plates —
Design them like custom lunch boxes 🍱.

Everyone should get only what they ordered, not a pile of things they’ll never eat. 


πŸ›  D – Dependency Inversion Principle (DIP)

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

πŸ“‘ LinkedIn Analogy:

NotificationService should depend on a generic Notifier,
not directly on Email, SMS, or Push.


✅ Good Design:


interface Notifier { void send(String msg); } class EmailNotifier implements Notifier {...} class PushNotifier implements Notifier {...} class NotificationService { Notifier notifier; NotificationService(Notifier notifier) { this.notifier = notifier; } void alert(String msg) { notifier.send(msg); } }


Bad Example (DIP Violation)


class EmailNotifier { void send(String msg) { System.out.println("Sending email: " + msg); } } class NotificationService { private EmailNotifier emailNotifier; // Directly depending on low-level EmailNotifier NotificationService() { this.emailNotifier = new EmailNotifier(); } void alert(String msg) { emailNotifier.send(msg); // Directly coupled to EmailNotifier } }

Why this is bad:

  • NotificationService is directly coupled to EmailNotifier — meaning it can only send emails, and cannot be easily changed to use SMS, Push, etc.

  • If tomorrow, we want to add SMS or Push notification functionality, we’ll have to modify NotificationService to handle that explicitly, which violates DIP. 😩

What’s the problem here?

  • If you want to switch out EmailNotifier for PushNotifier or add new functionality, you would need to change the NotificationService class every time. This creates a maintenance nightmare.


⚙️ Why Is DIP Important?

  • Decoupling: By depending on abstractions, high-level modules (like NotificationService) can be independent of the concrete details of low-level modules (like EmailNotifier, SMSNotifier, etc.). This makes it easier to extend the system by adding new Notifier types without changing the NotificationService.

  • Flexibility: New notification systems can be introduced without affecting the existing code.

  • Testability: We can mock any notifier (like PushNotifier) easily for unit testing, without worrying about actual network calls being made.


🎯 Wrapping Up

🧠 SOLID = Strong Object design Leads to Ideal Design.

You don’t need to memorize.
Just remember:

  • S – One job per class πŸ‘·‍♂️

  • O – New features without touching old code πŸ§ͺ

  • L – Don’t break inheritance πŸ“‰

  • I – Small, focused interfaces πŸ”¬

  • D – Depend on interfaces, not concrete chaos πŸ’£


πŸ’¬ Tell me in comments:

  • Which principle tripped you the most?

  • Any other examples you'd like with Instagram or Amazon?


Stay curious, stay nerdy,
– Anand πŸš€

Comments

Popular posts from this blog

🐱 Tomcat vs ⚡ Netty – Which One Should You Use?

🐱 Tomcat vs ⚡ Netty – Which One Should You Use? So recently I got curious about this too πŸ€”. Everywhere in Spring Boot tutorials we see Tomcat . Then suddenly while exploring Spring WebFlux , the name Netty pops up. And I was like – “Wait, who’s this Netty guy trying to replace Tomcat?” πŸ˜… Let’s break it down with real-time examples , icons , and fun comparisons . 🐱 Tomcat – The Traditional Web Server Type: Servlet Container (blocking I/O) World: Used with Spring MVC Style: Thread-per-request model πŸ‘©‍πŸ’» Pros: Stable, widely used, battle-tested Cons: Struggles with huge concurrent connections πŸ‘‰ Example in real life: Tomcat is like a restaurant with fixed waiters 🍴. - Each customer = one thread/waiter - If too many customers come in at once → waiters run out → customers wait outside πŸšͺ ⚡ Netty – The Reactive Rockstar Type: Asynchronous Event-Driven Network Framework World: Default for Spring WebFlux Style: Event-lo...

🎭 Spring’s Secret: Why @Transactional & Friends Betray You Silently

πŸ’‘ Lesson Learned — Not a Prod Bug, But a Real Pain No, this wasn’t a production outage. Nobody screamed at me. But I sat for 3 hours wondering: “Why the heck is my @Transactional not rolling back!?” 😡‍πŸ’« “Why is Redis cache not working?” 🀯 Turned out, the issue was one silent villain: 🧱 Self-invocation 🀷 What Is @Transactional ? If you're new: @Transactional = Tells Spring to start a DB transaction when a method is called. It’ll commit if everything’s okay. It’ll rollback if something fails. 🧠 Think of it like wrapping your code in: try { beginTransaction(); // your logic commit(); } catch(Exception e) { rollback(); } πŸ•΅️ Real-Life Analogy — The Gateway Community 🏘️ Let me tell you about my society — it has a strict watchman at the gate. Here’s how it works: πŸ›‚ Watchman = Spring Proxy 🏠 Your apartment = Your service class πŸšͺ Your room = A method inside that class πŸƒ Scenario 1: Outsider Visits Your friend from outside...

🌟 My Journey – From Zero to Senior Java Tech Lead 🌟

 There’s one thing I truly believe… If I can become a Java developer, then anyone in the world can. πŸ’― Sounds crazy? Let me take you back. πŸ•“ Back in 2015… I had zero coding knowledge . Not just that — I had no interest in coding either. But life has its own plans. In 2016, I got a chance to move to Bangalore and joined a Java course at a training center. That’s where it all started — Every day, every session made me feel like: "Ohhh! Even I can be a developer!" That course didn’t just teach Java — it gave me confidence . πŸ§ͺ Two Life-Changing Incidents 1️⃣ The Interview That Wasn't Planned Halfway through my course, I had to urgently travel to Chennai to donate blood to a family member. After that emotional rollercoaster, I found myself reflecting on my skills and the future. The next day, as I was preparing for my move to Bangalore to complete the remaining four months of my course, I randomly thought — "Let me test my skills... let me just see...