Isaac.

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 example
// 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 example
// 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 example
// 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 example
// 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

AspectInterfaceAbstract Class
InheritanceMultiple (C#, TS)Single
Runtime presenceNo (TS), Yes (C# interfaces are metadata)Yes
Contains implementationNo (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.

C# 8 Default Interface Method (short)
public interface ILogger
{
    void Log(string message);
    void LogError(string error) => Log($"ERROR: {error}");
}
Written for developers who like clarity, small examples, and readable code. Understanding these differences helps you choose the right tool for modeling relationships and behavior in your applications.