Abstract class vs interface: what are the differences, and when do you choose each?
The second most-asked CS question in the index: 28 reports, from Infosys, Deloitte, Accenture, Wipro, HCLTech, Luxoft and FIS.
Say this first · about 30 seconds
An abstract class is a parent class that a family of classes inherits from, and you can't create it on its own. It can hold fields, a and shared methods, plus abstract methods, which have no body, so every child must write them. A class can inherit only one class.
An interface lists what a class can do: method and property names, with no fields and no constructor. Since C# 8 a method can have a default body. A class can implement many interfaces.
For example, at BMW FS, is abstract: every loan and lease has a balance and the same payment code, written once.
is an interface: a contract and an insurance policy can both go on the monthly bill, but a policy has no balance, so it is not a kind of contract.
How I choose: if each type is really a kind of the parent and they share data, an abstract class. If unrelated types only share something they can do, an interface. Often I use both: callers depend on the interface, and an abstract class shares the code behind it.
Make it yours · UDC Finance deal checks (invented example)
Situation: at UDC Finance in New Zealand, every deal runs through checks before it goes live. Our team writes some checks; UDC's own developers write others.
What I did: talks to every check through one interface, . Our checks all share one rule (the term must be above 0), so I wrote it once in an abstract class, , and each of our checks adds only its own rule.
Result: UDC's implements directly, without our base class, and runs it the same way. A new check is one new class and one line in the list; the loop never changes.
What I'd say: the interface is the contract the caller depends on. The abstract class is shared code for our own checks; no one is forced to inherit it.
The code, simplified
Invented to show the shape, in C#. Tap Next to walk one deal through both checks.
UDC.sln · where these 5 files sit
Checks/
Diagram · step 1 builds its list: our check and UDC's, both as .
DealActivationcalls checks only through IDealCheck
NzLeaseCheckvia DealCheckBase
CreditLimitCheckUDC's own
list: 2 IDealCheck
IDealCheck.cs · line 3 · 1 of 1Declares the one method every check must have. It returns the problems found; an empty list means the deal passes.
Fix later in your repoExample written for this card: invented, not from a real codebase.
Say it out loud, then check
Show the 30-second answer
An abstract class is a parent class that a family of classes inherits from, and you can't create it on its own. It can hold fields, a and shared methods, plus abstract methods, which have no body, so every child must write them. A class can inherit only one class.
An interface lists what a class can do: method and property names, with no fields and no constructor. Since C# 8 a method can have a default body. A class can implement many interfaces.
For example, at BMW FS, is abstract: every loan and lease has a balance and the same payment code, written once.
is an interface: a contract and an insurance policy can both go on the monthly bill, but a policy has no balance, so it is not a kind of contract.
How I choose: if each type is really a kind of the parent and they share data, an abstract class. If unrelated types only share something they can do, an interface. Often I use both: callers depend on the interface, and an abstract class shares the code behind it.
Check your answer: tick what you said
You said 0 of 8
Then add your example: UDC Finance deal checks (invented example)
Situation: at UDC Finance in New Zealand, every deal runs through checks before it goes live. Our team writes some checks; UDC's own developers write others.
What I did: talks to every check through one interface, . Our checks all share one rule (the term must be above 0), so I wrote it once in an abstract class, , and each of our checks adds only its own rule.
Result: UDC's implements directly, without our base class, and runs it the same way. A new check is one new class and one line in the list; the loop never changes.
What I'd say: the interface is the contract the caller depends on. The abstract class is shared code for our own checks; no one is forced to inherit it.
See it
Billing runone bill per customer
Policy joba second bill
Contractabstract class: a family
HoldsBalance, BillTo
Codeconstructor, ApplyPayment()
Loanis a Contract
Leaseis a Contract
CarPolicybase: Policy
1/9Before: billing loops a list of : loans and leases. Insurance has its own job and bill.One bill, please. Make a ? It already has a base, and C# allows only one.An is a family's base: it holds data and shared code.An lists only what a class can do. No data, no constructor. implements . So does , the base of . Two families, one interface.Billing now gets a of : a Lease and a CarPolicy side by side.It calls b.AmountDue(). This object is a Lease, so Lease's runs: 460.Same line, next object: a CarPolicy. Its own returns the premium, 35. No if.Abstract class: what a family is and shares. Interface: what anything can do, across families.
In plain words
A loan and a lease are both : the customer owes a balance and pays it down monthly. An insurance policy is different: the customer pays a monthly price for cover and owes nothing else.
An abstract class is a half-built house in a housing estate. The foundation and the pipes are already in, the same for every house. Each buyer finishes the rooms. You can't live in the half-built house itself.
An interface is a driving licence. It says you can drive. A teacher, a doctor or a taxi driver can all hold one; it says nothing about who they are. And one person can hold many licences.
So: one house plan per house (one base class), but as many licences as you need (many interfaces).
The senior answer · about 2 minutes
What each one really is
An abstract class is an unfinished base class. It can have fields, a constructor and methods with real code, so it can keep its data valid for every child. For example, our constructor throws if the balance is negative, so no loan or lease can be built broken.
An interface only says what a class can do. It has no fields and no constructor, so it can't protect any data; each class that implements it decides how. For example, says "you have an amount due and a name to bill", and a contract and an insurance policy each answer in their own way.
The big practical difference is the count. A class gets exactly one base class, and that base brings its data and constructor along. It can implement as many interfaces as it needs. So I spend the base class on what a type is, and use interfaces for what it can do.
How I choose
First I ask: is each type really a kind of the parent, and can it keep every promise the parent makes? A lease is a contract: it has a balance and takes payments. If yes, and they share data, I use an abstract class.
If unrelated types only share an ability, I use an interface. An insurance policy goes on the same bill, but it has no balance to pay down. Making it a contract would mean a fake balance and a payment method that throws, so it implements instead.
If the only reason for a child class is one behaviour that changes, like a late-fee rule that differs per client, I don't subclass at all. The class holds that rule as an object behind an interface. That is .
And the code that uses these objects depends on the interface. The asks for a list of , so a test can hand it a fake in one line.
Default interface methods
Since C# 8, an interface method can have a body. That was added for one job: adding a member to an interface that many classes already implement, without breaking them. Without a body, every class that implements it stops compiling. Worse, a class in another team's DLL, the compiled file their project ships as, fails only when the app loads it.
It does not make an interface an abstract class. There is still no state and no constructor, and the default runs only when you call through the interface type. On a variable the method isn't even visible.
There is one trap I watch for. If the base class implements the interface but never writes that method, the default body is used for the whole family. A child that later writes a method with the same name does not replace it: calls through the interface still run the default. So I write the method in the base as , and children override it.
In production
Both together is the shape I use most. Callers and the see an interface; an abstract base shares the code behind it; and anyone can still implement the interface without our base.
For an interface that other teams build against, I treat every new member as a breaking change, meaning a change that can stop their code working. It gets a default body, and our build runs a check that compares the new version with the last release. We added that after a Navistar billing job failed to start (see the incident).
One lease, both tools at work
A lease is loaded, billed and paid. Watch which part of the work the abstract class does, and which the interface does.
Azure SQLlease 42
Lease objectContract part + Lease part
BillingRunholds it as IBillable
PaymentServiceholds it as Contract
row 42 → new Lease(...)
Contract part: 9,000 ✓
1/7Abstract class: EF Core builds a Lease. Contract's constructor runs first and checks the balance.Then Lease's constructor adds its own part. One object now holds both parts.Interface: billing puts it in a list of . It sees only and .b.AmountDue(): the object is a Lease, so Lease's override runs.b.Summary(): no class wrote one, so the interface's default body runs.Abstract class again: a payment. The service holds a and calls the shared .A policy can be billed the same way, but never paid down: it is not a .
The story
Before
BMW FS bills loans and leases through the abstract . Its insurance partner's car policies came on a separate bill, and customers asked for one. Here are the two tempting fixes, and why both go wrong.
ContractBalance, ApplyPayment()
Policypremium only
1/6Fix 1: make inherit . Then billing's list of would take it.But a policy has no balance. Contract's constructor still wants one, so the code passes a fake 0.And it gets , which means nothing for a policy. One day someone calls it.Fix 2: a new abstract above both. It builds, but it uses up each class's one base.Next ask: export contracts, not policies. Each class has only one base, so the chain gets deeper.Interfaces: each class implements only the abilities it has. Contract implements two, Policy one.
Why it had to change
A base class is a one-time choice: a class gets exactly one, and it brings all its data and its constructor along. Spending it on an ability like "can be billed" either drags fake data into types that don't have any, or leaves no room for the next ability.
How it works now
With an interface, the billing loop is foreach (IBillable b in due) b.AmountDue();. Here is how .NET finds the right code for each object while the app runs, including a default body that no class wrote (the for classes is on its page):
Billingthe loop
btype: IBillable
Objectthe real one
Its classwho fills each name
has AmountDue? yes
1/5At build time the compiler sees only IBillable b. It has , so the line builds.While the app runs, b holds a to an object. .NET follows it.The object says CarPolicy. .NET keeps a list per class: which method runs for each interface method.CarPolicy's map: → its override. It runs and returns the premium.Then b.Summary(). No class wrote one, so the map points at the interface's default body.
What we gained
One bill for both families, with no fake balance on a policy. The next ability is one more interface, implemented only by the classes that have it. And a billing test can pass in any tiny test class that implements .
In the lending API
The contract family's base. It is abstract and implements ; the comments mark what only an abstract class can do.
Only the key lines are shown; Deep walks the whole file.
Lending.sln · where this file sits
src/Domain/Contracts/
⋯ 5 lines in Deep⋯ 3 lines in Deep⋯ 10 lines in Deep
Contract.cs · line 3 · 1 of 4: nobody can write new Contract(...); every real one is a Loan or a Lease. : IBillable implements the interface, so every child implements it too.
The code, file by file
The billing types in the lending API: one interface, two abstract families, and the that bills them together. Tap Next to walk the diagram with the code.
Lending.sln · where these 6 files sit
src/
Domain/
Billing/
Contracts/
Insurance/
Application/Billing/
Diagram · step 4An lists only what a class can do. No data, no constructor.
Billing runone bill per customer
Policy joba second bill
Contractabstract class: a family
HoldsBalance, BillTo
Codeconstructor, ApplyPayment()
Loanis a Contract
Leaseis a Contract
CarPolicybase: Policy
IBillable.cs · line 4 · 1 of 5An interface: names only. No fields, no constructor. You can't new IBillable(), but a variable can have this type and hold any object that implemented it.
What they ask next
The follow-ups interviews really ask. Answer out loud, then tap to check.
ReportedCan an abstract class have a constructor? How is it called?
Yes. You can't call it with , but every child calls it with in its own constructor, and it runs first. It sets up the parent part of the child object and runs the shared checks. I make it , since only children can use it.
ReportedTwo interfaces have the same default method. What happens?
It builds. They are two different methods that share a name, and a class doesn't get either default as its own. So lease.Describe() doesn't compile, while ((IStatementLine)lease).Describe() runs that interface's body. If the class writes its own , both interfaces use it. The second live task below walks this code.
ReportedCan a class inherit an abstract class and implement interfaces at once? In what order?
Yes: one base class, then any number of interfaces, base class first. class Lease : Contract, IExportable builds; class Lease : IExportable, Contract fails with CS1722: the rule is that the base class must come first, before any interface.
Answer, then open the next level. That is the order an interviewer digs in.
Reporteda candidate was asked Intervieweran interviewer asks it
ReportedCan an abstract class have a constructor? How is it called?
Yes. You can't call it with , but every child calls it with in its own constructor, and it runs first. It sets up the parent part of the child object and runs the shared checks. I make it , since only children can use it.
The compiler calls the parent's constructor that takes no values. If the parent has none, like , the child doesn't build: error CS7036, "no argument given". That is on purpose: no contract can be built without a balance.
Can an interface have a constructor or fields?
No. An interface can't hold per-object data, so there is nothing to set up. Since C# 8 it can have static members and default method bodies, but never an instance field or a constructor.
ReportedTwo interfaces have the same default method. What happens?
It builds. They are two different methods that share a name, and a class doesn't get either default as its own. So lease.Describe() doesn't compile, while ((IStatementLine)lease).Describe() runs that interface's body. If the class writes its own , both interfaces use it. The second live task below walks this code.
When both interfaces extend one parent interface and each gives a default for the same parent method. Then the class has two equally good answers for one method: error CS8705, "does not have a most specific implementation". The class must write its own.
Explain this lineWhat is explicit interface implementation?
Writing a method as string IAuditLine.Describe() ties it to that one interface, so each interface can get its own answer. It is on the .
ReportedCan a class inherit an abstract class and implement interfaces at once? In what order?
Yes: one base class, then any number of interfaces, base class first. class Lease : Contract, IExportable builds; class Lease : IExportable, Contract fails with CS1722: the rule is that the base class must come first, before any interface.
InterviewerWhy not…With default methods, isn't an interface the same as an abstract class now?
No. An interface still can't hold per-object data or have a constructor, so it can't keep anything valid. Default bodies were added so you can add a member to an interface without breaking every class that already implements it. I use them for that, not to share code. They need .NET Core 3.0 or later; old .NET Framework apps can't use them. The incident below is what happens without one.
Adding a normal method to an abstract class rarely breaks anyone: children just get it (unless one already has a method with that name). Adding a member without a body to an interface breaks every class that implemented it. Adding an abstract method breaks every child. So for types other teams build on, I add only bodies: normal methods, or default interface methods.
ReportedCan we achieve 100% abstraction?
The textbook answer: an abstract class can be anywhere from 0 to 100% abstract, and an interface with no default bodies is 100%: it says only what, never how. I'd add the part that matters: the depends on and knows nothing about how any class works, and that is what makes it easy to test and change.
ReportedWhat is the difference between an abstract class and a normal class?
A normal class can be built with and every method has a body. An abstract class can't be built, and it may have methods with no body that children must write. I make a class abstract when a plain one would make no sense on its own, like a contract that is neither a loan nor a lease.
Why not…Should every class have an interface, like ?
No. I add one when something needs to swap the class: a test fake for something slow or external, or more than one real version. An interface for a class that has one version and is easy to test only adds a file to open. The has the longer answer.
Production bugs, and how to tell them
Each one: what you see, why, the fix in code, and the line to say.
The lease's own summary line never reaches the bill
BMW FS call centre ticket:
"My bill line doesn't mention the service plan.
Is it still included?"
(no exception; the Lease unit test passes)
Cause
gave a default body. implements but never wrote , so when .NET links each interface method to the code that runs it, was linked to the interface's default, once, for every contract. Later added its own plain public string Summary(). That is a separate method that only shares the name: Lease did not implement itself, so the link was never redone, and calls through still ran the default. No warning: Contract has no for the compiler to say Lease is hiding.
Fix
writes public virtual string Summary() with the default text, so is now linked to Contract's method, and that method is . overrides it, so the call follows the real object again. A test calls through an variable, the way billing does.
Diagram · step 3That was decided once, for all contracts: wrote no Summary, so the default.
Unit testLease l; l.Summary()
Leaseown Summary(): + plan
Asha Rao: 460.00 incl. service plan
Before · Contract.cs · line 1 · 1 of 1Implements and writes no , so for every contract the interface uses its default body.
Say it
It was a default interface method trap. The base class implemented the interface but never wrote that method, so the interface's default body was used for every child, and a child's method with the same name was never used through the interface, and the compiler gave no warning. The tests called it on a Lease variable, which hid it. We made the method virtual in the base and overrode it in the child, and the test now calls through the interface, the way production does.
Production incident: Truck-lease bills not sent: a new interface member broke another team's add-on
P2 · serious, has a workaroundNavistar Financialcame with a deployMonthly billing job in the servicing API, with Navistar's truck-lease add-on
Composed, not a real event at Navistar Financial. The mechanism is real .NET: when an interface gains a member with no body, a class compiled against the old version fails with TypeLoadException the moment the app loads it. The names, counts and times are composed.
The story in 5 lines
On 1 Nov the monthly billing job stopped at start-up, and no Navistar Financial truck-lease bills were made.
The error was TypeLoadException, which .NET throws when it can't load a class: the add-on's "does not have an implementation" of .
Our shared billing package (a library we publish for other teams to build on) went to version 2.0, which added string Summary(); to with no body. We updated our own classes; the add-on, a DLL (the compiled file of a project) built by another team, was still built against 1.4.
Fix: give a default body, so a class that never heard of it still loads. Release 2.0.1 the same morning; the old add-on loaded unchanged.
Prevent: the package build now compares each new version with the last release and fails if old code would break; and a new interface member must come with a default body.
Who's who (5 people)
You
.NET developer on the vendor team. Owns the shared billing package; finds the cause, writes the fix.
Tech lead
Vendor senior. Reviews every change, runs the postmortem.
L2 pod
Vendor support. First look, reads the alerts and logs.
Add-on team
Another vendor team. Builds Navistar's truck-lease add-on as its own DLL against our package.
Navistar billing lead
Owns customer bills. Decides when late bills go out.
Tap a step. The map lights the place where it happens.
Billing jobloads the add-on at start
TruckLeaseBilladd-on DLL, built on 1.4
System.TypeLoadException: Method 'Summary'
in type 'TruckLeaseBill' does not have
an implementation.
1/4 Detect
Detect: the job dies at start-up, before billing anything. Alert: TypeLoadException.
Sat 1 Nov, 00:30 CT (11:00 IST)Azure Monitor alert, then the L2 pod
What: the alert "billing job failed at start" fires 2 minutes after the job's scheduled run. No Navistar truck-lease bills exist for November.
Why it showed fast: the failure is loud. The runtime throws the first time it loads the add-on's class, before a single bill is made.
How we saw it: runs the query below. One exception, from one type, in the add-on's DLL.
App Insights: exceptions in the billing job
exceptions
| where timestamp > ago(1h)
| where cloud_RoleName == "billing-job"
| summarize count() by type,
outerMessage
// System.TypeLoadException · 1// Method 'Summary' in type// 'Navistar.AddOn.TruckLeaseBill'// does not have an implementation.
In theory
A release of the shared package triggers a rebuild and test of every add-on that uses it, before the monthly run.
In practice
The add-on lives in another team's repo, and nobody linked the two. We found out at the monthly run.
Try it yourself · 15 minutes, on your Mac
dotnet new classlib -n Billing. Write public interface IBillable { decimal AmountDue(); }.
dotnet new classlib -n AddOn, reference Billing, write public class Truck : IBillable { public decimal AmountDue() => 1m; } and build it.
dotnet new console -n Job, reference both projects, and create a with Activator.CreateInstance(typeof(Truck)). Run it: fine.
Add string Summary(); to and build only Billing. Copy Billing/bin/Debug/net8.0/Billing.dll over the one in Job/bin/Debug/net8.0/, then run the built Job directly (dotnet Job/bin/Debug/net8.0/Job.dll, not dotnet run, which would rebuild everything): TypeLoadException.
Give a body (=> "";), build Billing, copy the DLL again, run the same way: it loads.
You should see
Adding a member with no body to an interface breaks classes built before it, even ones you never compile. A default body keeps them working.
The senior move
"Once another team builds on our interface, I treat every new member as a breaking change: it gets a default body, and the package build checks old code still fits."
SourcesSourced: TypeLoadException for a missing interface member, and default interface methods as the way to add members safely (C# docs, "Update interfaces with default interface methods"); package validation (.NET docs). Composed: Navistar Financial's systems, the add-on, dates, counts, names, ticket and numbers, and the (standard columns).
Traps
Don't sayAn interface can't have any code.SaySince C# 8 it can have default method bodies. It still can't hold per-object data or have a constructor.
Don't sayUse an abstract class whenever classes share code.SayOnly when they are really one family and every child keeps the parent's promises. For shared code alone, composition.
Don't sayInterfaces give C# multiple inheritance.SayA class implements many interfaces but inherits no data or constructor from them: many abilities, still one parent.
Traps
What should you say instead? Tap to check.
Don't sayAn interface can't have any code.
Say instead
Since C# 8 it can have default method bodies. It still can't hold per-object data or have a constructor.
Don't sayUse an abstract class whenever classes share code.
Say instead
Only when they are really one family and every child keeps the parent's promises. For shared code alone, composition.
Don't sayInterfaces give C# multiple inheritance.
Say instead
A class implements many interfaces but inherits no data or constructor from them: many abilities, still one parent.
Live coding
Easy first, then harder. Set a timer and type it before you look.
Task 1
Question index: Musteasy · 20 minutes
Put loans and car insurance policies on one monthly bill. stays the abstract base for loans and leases; already inherits . Design the types and a billing method, and prove it with a test.
What do a loan and a policy really have in common: data, or something they can do?
What type should the billing method's list have?
Show a solution
Lending.sln · where this file sits
tests/Application.Tests/
BillingTests.cs · line 7 · 1 of 3Two families in one list, typed by the interface they both implement.
What the interviewer checks
An interface with and , implemented by both and .
No fake balance on a policy, and no on it.
The billing method takes , with no if on the type.
decimal for money.
You can say why stays abstract: shared data and the balance check.
Senior extra: a tiny test class that implements proves the billing method needs nothing else.
Task 2
Reportedmedium · 15 minutes
What does this program do? For each of the three marked lines, say whether it compiles and, if it does, what it prints. Then make lease.Describe() work.
Does a class get an interface's default body as its own method?
Lines 2 and 3: which variable type picks the body?
What happens to both interfaces if writes its own ?
Show a solution
Lending.sln · where these 2 files sit
Diagram · step 1Line 1: has no of its own. The defaults belong to the interfaces. CS1061.
Lease objectno Describe
IStatementLinedefault body
IAuditLinedefault body
lease.Describe()
Program.cs · line 2 · 1 of 3A class doesn't inherit a default body as its own member. Remove this line to run the rest.
What the interviewer checks
Line 1 does not compile (CS1061): has no of its own.
With line 1 removed: line 2 prints "statement", line 3 prints "audit".
Two same-named defaults are not a conflict: they are separate methods.
Fix: writes public string Describe(); then all three lines print its text.
Senior extra: name when it does fail (CS8705: both override one parent interface's method).
With AI tools
Copilot and other assistants tend to write an interface for every class ( with one version and no test fake) and a new abstract base whenever two classes share a few lines, because both are common in the code they learned from. In review I ask two questions: does anything really swap this interface, and is each child truly a kind of its abstract base? I also check any new member on a shared interface: it needs a default body, or every other team's build breaks.