Inheritance is Dead? Go Says Yes, Java Says Never!
Okay, I'll admit the title is total clickbait. If you came here for a hot take on why Java sucks and Go is its rightful heir in big tech, then sorry to say, I tricked you. Hehe. But hey, since you're already here, why not stick around? You might just gain a new perspective on how different design paradigms can help build software for a future we can't even imagine yet.
For context, I've used both Java and Go long enough to develop a healthy love-hate relationship with each. This gives me a pretty clear view of their actual differences—not just in terms of verbosity, performance, or fandom, but in their core ideologies for writing software: inheritance versus composition.
Before we dive in, let's take a quick look at the history of both languages. Feel free to skip ahead if you're already familiar with their origin stories or just don't care.

The History of Java and Go
Java's story began in 1991 at Sun Microsystems with the "Green Project," led by James Gosling. The team wanted to create a language for digital devices. It was first called "Oak," but got renamed to "Java" because of trademark issues (because why not?). Its platform independence made it perfect for the booming internet, and it was officially released in 1995. Since then, the world has adopted it so hard that it now supposedly powers almost THREE BILLION DEVICES every single day. Can you even imagine that? Servers, tools, you name it—Java has a huge piece of any pie that's even remotely related to tech.
Meanwhile, Go was designed at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson, and was publicly announced in November 2009. It's syntactically similar to C but adds modern features like memory safety, garbage collection, and structural typing. It's often called Golang to avoid confusion, but its proper name is just Go.
Go is the cool new kid on the block, and its biggest selling point is treating concurrency as a first-class citizen—unlike me at social gatherings (you know, the guy in the corner furiously typing on his calculator app to look busy).
What the Heck is Inheritance?
Inheritance in programming is similar to what it means in regular terms: one thing getting properties or behaviors from another.
While the real-world concept is simple, its programming counterpart tends to split developers into two camps:
a) Those who find it perfectly intuitive.
b) THOSE WHO WANT TO BANG THEIR HEADS ON THE KEYBOARD REPEATEDLY.
There is no in-between.
Essentially, inheritance is a way to design software that creates a hierarchy based on an "is-a" relationship. A new class can inherit the properties and methods of an existing class.
For example, a Poodle is a Dog, and a Dog is an Animal. The Poodle class would automatically get all the dog-like behaviors (like a bark() method) from the Dog class, which in turn gets animal-like properties (like numberOfLegs) from the Animal class. This focus on a strict, hierarchical structure is the cornerstone of the inheritance model.
The reason I've put Java in the inheritance corner for this showdown is that it was hugely influential in promoting this paradigm right from its birth. Object-Oriented Programming with strong inheritance is baked into Java's DNA.
And What's This "Composition" Thing?
You might not hear "composition" thrown around as much as "inheritance," but its philosophy is the engine behind modern languages like Go and Rust.
In simple terms, it means... well, composing something. Instead of a class inheriting traits from a parent class, you build a class by giving it the things it needs. It’s less "becoming" and more "acquiring." Instead of things inheriting from other things, we focus on attaching a thing to another thing (yes, too much thinging, I know heh).
This approach works on the principle of a "has-a" relationship. Let's go back to our Poodle. Instead of a Poodle being an Animal, we'd say:
-
A Poodle has a BarkingAbility.
-
A Poodle has FourLegs.
The BarkingAbility and FourLegs are independent components that we "plug into" our Poodle object to give it functionality. Think of it like building with LEGOs—you combine different bricks to make what you want.
I chose Go for the composition corner because this idea is deeply rooted in the language. Go intentionally leaves out classical inheritance, encouraging developers from the start to build software by composing smaller, reusable pieces.
Coding examples
Let's see an example of inhertiance in Java
// Base class class Animal { public void eat() { System.out.println("This animal is eating."); } } // Dog inherits from Animal class Dog extends Animal { public void bark() { System.out.println("Woof! Woof!"); } } // Poodle inherits from Dog class Poodle extends Dog { public void prance() { System.out.println("The poodle is prancing stylishly."); } } // Main class to run the example public class Main { public static void main(String[] args) { Poodle myPoodle = new Poodle(); // Calling methods from every level of the hierarchy myPoodle.eat(); // From the Animal class myPoodle.bark(); // From the Dog class myPoodle.prance(); // From the Poodle class } }
Now, let's look at an example of composition in Go
package main import "fmt" // A component that provides walking behavior type Walker struct {} func (w Walker) Move() { fmt.Println("Walking on four legs.") } // A component that provides barking behavior type Barker struct {} func (b Barker) Bark() { fmt.Println("Woof! Woof!") } // The Poodle struct is COMPOSED of other types. // It embeds a Walker and a Barker to gain their functionality. type Poodle struct { Name string Walker // Poodle "has-a" Walker Barker // Poodle "has-a" Barker } func main() { // We construct a Poodle by giving it its component parts. myPoodle := Poodle{ Name: "Charlie", Walker: Walker{}, Barker: Barker{}, } fmt.Printf("Meet %s, our composed poodle.\n", myPoodle.Name) // We can call the methods of the embedded components directly. myPoodle.Move() myPoodle.Bark() }
Real Life analogy
Alright, enough with poodles. As I'm sitting here, I can hear the traffic humming on the main road. So let's talk about something more relatable: cars. We're not just going to design any car; we're going to design a dream machine, a BMW M series.
How would our two competing philosophies handle this?
The Inheritance Way:
With inheritance, we think in terms of lineage. We build a rigid hierarchy, starting broad and getting more specific at each step. It’s like a biological classification.
Vehicle: At the very top, we have the great-grandparent, Vehicle. It's super generic. It knows it has a weight and can move(). That's about it.
Car: Next, a Car is a Vehicle. We extend Vehicle and add car-specific things. Now it has fourWheels, doors, and a honk() method.
BMW: A BMW is a Car. Now we extend Car and add the brand-specific luxury. It gets the iconic kidneyGrille property and maybe an enableComfortMode() method.
BMWM: Finally, our BMWM is a BMW. We extend BMW to add the high-performance magic. It inherits everything above it and adds a sportSuspension and a thrilling launchControl() method.
This looks clean and logical, like a perfect set of Russian nesting dolls. But what happens when you want to build a BMW motorcycle? It's a Vehicle, but it's not a Car. And what if that motorcycle shares the same awesome infotainment system as the car? You can't inherit from both the BMW car class and a new Motorcycle class. You've hit a wall. This is where the rigid family tree starts to creak and groan.
The Composition Way:
Composition throws the family tree out the window. It doesn't care about ancestors. It asks one simple question: "What is this thing made of?"
We start with an empty BMWM struct. It's just a shell. Then, we go shopping for parts.
Our BMWM has an Engine. We don't inherit from a generic engine; we bolt in a specific V8_Turbo component.
It has Wheels. We'll give it a PerformanceTireSet component.
It has a Transmission. We'll plug in a DualClutch_8_Speed component.
It has an InfotainmentSystem.
Here's the magic: this exact same InfotainmentSystem component can be plugged into our car, a BMW motorcycle, or a hypothetical BMW boat. No drama, no restructuring. It's just a part.
You see the difference? We're not inheriting a legacy; we're building a machine from a collection of focused, independent parts. If we want to create a prototype with an electric motor, we don't refactor the whole family tree. We just swap the V8_Turbo engine component for a HighTorque_ElectricMotor component.
It’s the difference between being defined by your ancestors versus being defined by the parts you choose for yourself. Kind of a deep thought for a Monday morning before I've even had my second coffee.
Why do I care even if I code in these languages ?
Right now, it's Monday morning. As I sit here in the library of my college, probably not far from you, the last thing on my mind should be programming philosophy. I should be thinking about how to survive one more semester or getting a real job instead of posting about all this shit.
And you might be thinking the same thing: "I use Java/Go, my code runs, I get paid. Why does this 'is-a' vs. 'has-a' debate matter to me?"
It matters more than you think. This isn't just academic fluff; it's the foundation of how you solve problems and, more importantly, how much of a headache you create for your future self.

If You Code in Java...
You live in a world built on inheritance. But blindly using it everywhere is like building a skyscraper with only one, massive central pillar. The moment a crack appears at the bottom, the whole thing becomes unstable.
This is the "fragile base class" problem. You change one thing in a parent class, and suddenly, ten different child classes you forgot about break in mysterious ways. Modern Java development is all about knowing when to break the rules. The principle "favor composition over inheritance" is now chanted by seasoned Java architects. Understanding this helps you write flexible, less-buggy code that won't make your colleagues (or you, six months from now) want to throw their laptop out the window.
If You Code in Go...
You don't get a choice. Go took away inheritance, forcing you to use composition. So for you, the question isn't if you should care, but how to do it well.
Just because Go gives you LEGO bricks doesn't mean you'll automatically build a masterpiece. You can still create a chaotic mess. Understanding composition deeply means you can design clean, small, and truly reusable components. It's the difference between building a scalable microservice and creating a "distributed monolith" where every piece is tangled up with every other piece. Mastering composition is the key to unlocking Go's true power: simplicity and scalability.
Conclusion
Ultimately, this isn't about which language is better. It's about which tool is right for the job and how well you use it. Understanding these core design principles makes you a better software architect, not just a coder. It dictates whether the project you're working on today becomes a joy to maintain or a legacy nightmare tomorrow.
Hope you've enjoyed reading so far! We'll meet in some other blog, until then keep learning!!!