The question of when to use an interface versus an abstract class is a common topic in technical interviews and system design discussions. While many textbook definitions explain the differences between these two, I think it is easier to understand the concept by using a scenario plus sample code.
In this article, I’ll walk through two real-world scenarios with C# code examples to demonstrate when to choose an abstract class and when to prefer an interface.
Scenario 1. Abstract Class does work, Interface doesn’t
Consider we are creating an RPG game.
Where is the character?
- Has initial HP.
- Will sleep to restore HP and
- Will level up.
The player can choose an occupation to be either a Warrior or a Healer.
Where is a Warrior?
- Able to battle
- customized levels up for a warrior
And for a Healer
- able to self-healing
- customized level-up for a healer
We used abstract class for our RPG game character design, first we created an abstract-based class.
public abstract class Human
{
public int HP { get; set; }
public readonly int INITIAL_HEAL_POINT = 100;
public readonly int SLEEP_RESTORE_POINT = 20;
protected Human()
{
HP = INITIAL_HEAL_POINT;
}
public abstract void LevelUp();
public void Sleep()
{
HP += SLEEP_RESTORE_POINT;
Console.WriteLine($"Hero gets sleep and restores HP to: {HP}\n");
}
public virtual void SelfHealing(int healItem) { }
public virtual void Battle(int damage) { }
}
From the above base class, we can see.
- I used abstract for the LevelUp method, which enforces derived class to override the LevelUp method since level upgrading is unique by character’s occupation.
- For special skills like Battle and SelfHealing, I used virtual tools to give the derived class flexibility if they want to override it or just ignore it.
- The sleep method, since it is a shared method ( in our case, sleep just restores 20 HP regardless of the character occupation), does not need to be overridden; the method’s logic is complete and applies universally to all derived classes, so I just put void as a regular method, without abstract or virtual.
Here is the code of the Warrior class that inherited the Human base class.
public class Warrior : Human
{
public override void LevelUp()
{
Console.WriteLine("Some customized level upgrading mechanism for warrior type hero");
}
public override void Battle(int damage)
{
HP -= damage;
Console.WriteLine($"Hero takes {damage} HP from battle, and current HP is {HP}");
}
}
As you can see, in my Warrior class.
- I do not need to have a constructor to initialize my HP.
- I override the LevelUP method logic that is customized to Warrior level-up.
- I complete the logic of the Battle method for Warrior.
- I DO NOT need to do anything about the self-healing method.
And here is the code of the Healer class that inherited the Human base class.
public class Healer : Human
{
public override void LevelUp()
{
Console.WriteLine("Some customized level upgrading mechanism for healer type hero");
}
public override void SelfHealing(int healItem)
{
HP += healItem;
Console.WriteLine($"Hero perform self healing of {healItem} point, and current HP is {HP} ");
}
}
As you can see, in my Healer class.
- I do not need to have a constructor to initialize my HP.
- I override the LevelUP method logic that customizes to the Healer level-up.
- I complete the logic of the SelfHealing method for Healers.
- I DO NOT need to do anything about the Battle method.
The main program will look like this.
Warrior warrior1 = new Warrior();
Console.WriteLine($"Warrior1 intial HP: {warrior1.HP} ");
warrior1.Battle(30);
warrior1.Sleep();
Healer healer1 = new Healer();
Console.WriteLine($"Healer1 intial HP: {healer1.HP} ");
healer1.SelfHealing(30);
healer1.Sleep();
Output

Now, I will use Interface to do the exact same thing.
First, the Interface.
public interface IHuman
{
public int HP { get; set; }
void LevelUp();
void Sleep();
void Battle(int damage);
void SelfHealing(int healItem);
}
And then the WarriorOfInterface and HealerOfInterface class that implements IHuman.
// Code of Warrior
public class WarriorOfInterface : IHuman
{
public int HP { get; set; }
public readonly int INITIAL_HEAL_POINT = 100;
public readonly int SLEEP_RESTORE_POINT = 20;
public WarriorOfInterface()
{
HP = INITIAL_HEAL_POINT;
}
public void Battle(int damage)
{
HP -= damage;
Console.WriteLine($"Hero take {damage} HP from battle, and current HP is {HP}");
}
public void SelfHealing(int healItem)
{
// Do nothing since warrior does not have the ability to self-heal
}
public void Sleep()
{
HP += SLEEP_RESTORE_POINT;
Console.WriteLine($"Hero gets sleep and restores HP to: {HP}\n");
}
public void LevelUp()
{
Console.WriteLine("Some customized level upgrading mechanism for warrior type hero");
}
}
// Code of Healer
public class HealerOfInterface : IHuman
{
public int HP { get; set; }
public readonly int INITIAL_HEAL_POINT = 100;
public readonly int SLEEP_RESTORE_POINT = 20;
public HealerOfInterface()
{
HP = INITIAL_HEAL_POINT;
}
public void Battle(int damage)
{
// Do nothing since healer does not have to battle
}
public void SelfHealing(int healItem)
{
HP += healItem;
Console.WriteLine($"Hero performs self-healing of {healItem} points, and current HP is {HP}");
}
public void Sleep()
{
HP += SLEEP_RESTORE_POINT;
Console.WriteLine($"Hero gets sleep and restores HP to: {HP}\n");
}
public void LevelUp()
{
Console.WriteLine("Some customized level upgrading mechanism for healer type hero");
}
}
And the main program.
//Main program
WarriorOfInterface warrior2 = new WarriorOfInterface();
Console.WriteLine($"Warrior2 intial HP: {warrior2.HP} ");
warrior2.Battle(30);
warrior2.Sleep();
HealerOfInterface healer2 = new HealerOfInterface();
Console.WriteLine($"Healer2 intial HP: {healer2.HP} ");
healer2.SelfHealing(30);
healer2.Sleep();
The code runs perfectly, and the output is exactly the same as the example that uses an abstract class. Why do I endorse abstract class in this scenario?
For a few reasons.
- Code duplication: each occupation class has to reimplement the Sleep() logic and HP initialization in the constructor, which leads to code duplication.
- Opportunities for bug: each occupation has to implement a method that is not related to themselves, like the Warrior class needs to implement (although it is a dummy) the SelfHealing method. Imagine we have like ten occupations and each occupation has a few of its own special skillset; this creates opportunities for bugs due to inconsistent behavior.
- Inconsistent state management: HP is a state of this example. Imagine a method like Battle in Healer class that does nothing might affect the HP calculation bug in the future, should our code get complex.
- Maintenance Overhead: If in the future we need to add new common methods like Eating, Transportation, Respawn, etc, in Interface, we have to implement it in each class, but with an abstract class, we just need to implement this common logic in the base class, therefore prevent code duplication.

Amit Kumar SinghPosted Dec 2, 2024, 3:50 PM
Thank you for sharing !