Interfaces vs Abstract Classes — C# & TypeScript
A practical, example-driven comparison.
Overview
Interfaces and abstract classes both let you model contracts and shared behavior. The trade-offs are about implementation vs. shape, runtime presence, and inheritance rules — and those trade-offs differ between C# and TypeScript.
C#
Interfaces
In C#, interfaces are pure contracts (though newer C# versions allow default interface methods). They define what a type must expose, not how.
// C# Interface
public interface IVehicle
{
string Make { get; set; }
string Model { get; set; }
void Start();
void Stop();
}
// Implementation
public class Car : IVehicle
{
public string Make { get; set; }
public string Model { get; set; }
public void Start() => Console.WriteLine("Car started");
public void Stop() => Console.WriteLine("Car stopped");
}Abstract Classes
Abstract classes can mix abstract and concrete members, can contain fields and constructors, and are ideal when you want shared implementation.
// C# Abstract Class
public abstract class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
private DateTime _manufactureDate;
// Concrete method
public void DisplayInfo() => Console.WriteLine($"{Make} {Model}");
// Abstract method
public abstract void Start();
public abstract void Stop();
}
// Implementation
public class Car : Vehicle
{
public override void Start() => Console.WriteLine("Car started");
public override void Stop() => Console.WriteLine("Car stopped");
}TypeScript
Interfaces
TypeScript interfaces describe object shapes and disappear at runtime. They're great for static type-checking and declaration merging.
// TypeScript Interface
interface IVehicle {
make: string;
model: string;
start(): void;
stop(): void;
}
// Implementation
class Car implements IVehicle {
constructor(public make: string, public model: string) {}
start(): void { console.log("Car started"); }
stop(): void { console.log("Car stopped"); }
}Abstract Classes
Abstract classes exist at runtime (they compile to JS), can include implemented methods, and are used when you want both a contract and shared behavior.
// TypeScript Abstract Class
abstract class Vehicle {
constructor(public make: string, public model: string) {}
// Concrete method
displayInfo(): void {
console.log(this.make + ' ' + this.model);
}
// Abstract methods
abstract start(): void;
abstract stop(): void;
}
// Implementation
class Car extends Vehicle {
start(): void { console.log("Car started"); }
stop(): void { console.log("Car stopped"); }
}Key Differences — Quick Table
| Aspect | Interface | Abstract Class |
|---|---|---|
| Inheritance | Multiple (C#, TS) | Single |
| Runtime presence | No (TS), Yes (C# interfaces are metadata) | Yes |
| Contains implementation | No (except C# default methods) | Yes |
When to Use Each
Use Interfaces
- When defining contracts for unrelated types.
- When you need multiple inheritance of shape.
- When you want lightweight, mockable APIs for testing.
Use Abstract Classes
- When sharing implementation code across types.
- When you need fields or constructors.
- When runtime behavior and instanceof checks matter.
Advanced & Patterns
C# 8 introduced default interface methods which blur the lines a little. In TypeScript, declaration merging and mixins provide flexible composition patterns.
public interface ILogger
{
void Log(string message);
void LogError(string error) => Log($"ERROR: {error}");
}