Last Updated: May 22, 2026
An interface is a contract: a list of members a class promises to provide, with no implementation attached to the contract itself. Where an abstract class lets you mix shared scaffolding with required behavior, an interface keeps the contract pure and leaves every detail to the implementing class. This chapter covers the traditional shape of interfaces in C#, how implementing classes wire themselves to a contract, and the role interfaces play in everyday e-commerce code.
interface KeywordAn interface declaration looks like a class with the bodies stripped out. The members describe what an implementer must offer, not how it must offer it.
The IDiscountable declaration has one method signature and nothing else. The PercentageDiscount class implements that signature by writing : IDiscountable after its name and providing a body for CalculateDiscount. The variable discount is typed as the interface, and the call goes through the interface contract to land on PercentageDiscount's body.
There's no class keyword in the declaration, only interface. And the method has no body, only a signature followed by a semicolon. That's the standard shape interfaces have used since C# 1.0.
I Naming ConventionEvery interface in the .NET ecosystem starts with a capital I. IDisposable, IEnumerable, IComparable, IList, IDiscountable. This isn't enforced by the compiler, but it's universally followed in C# code. Walk into any C# codebase and look for types starting with I followed by a capital letter, and you're almost certainly looking at an interface.
The reason is practical: code that uses interfaces tends to look like code that uses classes, and the prefix gives readers a fast visual cue about what they're dealing with.
The second signature reads as "any type that implements IDiscountable," not "an instance of a specific class." This shows up in IntelliSense, in code reviews, and in stack traces. Stick with the convention even for small interfaces.
| Common BCL interface | What it represents |
|---|---|
IDisposable | Owns resources that must be released |
IEnumerable<T> | Can be iterated with foreach |
IComparable<T> | Has a natural ordering |
IEquatable<T> | Has a value-based equality check |
IList<T> | An indexed, mutable collection |
A traditional interface (the shape that has existed since C# 1.0 and is still the most common form in day-to-day C# code) can declare four kinds of members: methods, properties, indexers, and events. None of them carry a body. The implementing class fills in the bodies.
Notice what's missing. There are no fields. There are no constructors. There are no access modifiers on these members, because every interface member is implicitly public. The compiler refuses public, private, protected, or internal on a traditional interface member.
If you try this, you get CS0106: The modifier 'public' is not valid for this item. The reason is that an interface only describes the external surface of an implementer. Anything that isn't exposed publicly isn't part of the contract, so there's nothing to declare here. (Default interface methods, added in C# 8, change this in narrow ways. The _Default Interface Methods_ lesson covers them.)
An interface also can't have any fields, because fields are state and interfaces don't carry state. You can declare properties (which look like state but are actually get and set method pairs), but the implementing class decides whether those properties are backed by fields, computed on the fly, or wrapped around something else.
The interface says Order must expose a TrackingNumber and a Weight. The class picks how to back each one. The interface doesn't know and doesn't care.
Calls through an interface go through a small lookup similar to virtual dispatch (an interface method table). The cost is negligible per call but enough that very hot loops sometimes use the concrete type directly. This isn't a concern until a profiler flags it.
To say "this class fulfills this contract," put a colon after the class name and list the interface. The class must then provide a public member matching every signature in the interface, or the compiler stops you with CS0535.
The class has to declare its members as public, because the interface members are implicitly public and a class member that implements them must match. If you forget public, the compiler treats your method as private and reports that the interface member isn't implemented.
If a class can't provide every member, the way out is to declare the class itself as abstract and leave the unfilled members for a concrete subclass:
This pattern shows up when you want to share part of an interface implementation in a base class while leaving the genuinely varying part to subclasses.
The most useful thing about interfaces is that you can declare variables, parameters, and return types using the interface itself. A variable typed as IDiscountable can hold any object whose class implements IDiscountable, without the calling code knowing which class.
The ApplyDiscount method accepts any IDiscountable. The same method handles both percentage and fixed-amount discounts without a switch, without inspecting the type, without knowing about either class. If you add a new discount class tomorrow (buy-one-get-one, loyalty tier, whatever), ApplyDiscount works on it as long as the new class implements the interface.
This is polymorphism through interfaces, and it's the structural payoff of having a contract type at all. The same pattern as polymorphism via base classes works through interfaces, with the difference that the implementer doesn't have to share a parent class. Two completely unrelated classes can implement the same interface and be used interchangeably.
A diagram of how the interface type sits between the caller and the implementations:
The caller talks to the interface. The interface dispatches to whichever implementation the actual object provides. New implementations slot in without the caller changing.
An interface can never declare a constructor and can never declare an instance field. Both rules follow from the same idea: an interface describes behavior, not data.
Without fields, an interface holds no instance state. Without constructors, you can never write new IBadIdea(...). The only way to get an object that fulfills the contract is to instantiate a class that implements it.
CS0144 says you can't create an instance of an abstract class or interface. The interface variable points to a concrete object, and new always names a concrete class.
This is one of the cleanest distinctions between interfaces and abstract classes. An abstract class can have fields, constructors, and concrete methods that hold and use state. An interface can't. If the contract needs to share state across implementations, an abstract class fits. If it only needs to describe behavior, an interface is the lighter, more flexible option. The _Abstract Class vs Interface_ lesson walks through the comparison in full.
Because interfaces hold no state, two classes implementing the same interface might use wildly different amounts of memory. The interface reveals nothing about the implementer's storage. A string Name property could be backed by a 1KB literal in one class and a 4MB lazy-loaded blob in another, and the interface looks identical.
Since C# 8, interfaces can declare static members, including methods, fields, and properties. These members belong to the interface itself, not to any implementing class, and they aren't part of the contract that implementers must fulfill.
The static method is called on the interface name itself: IShippable.CalculateBaseFee(...). Static members on interfaces are useful for shared helpers and constants that relate to the contract conceptually but don't belong to any one implementer.
C# 11 expanded this further with static abstract members and generic math interfaces, which is a deeper topic that the _Default Interface Methods_ lesson sets up. For now, just know that traditional interfaces (the ones you'll write 95% of the time) are about instance members, and static members are an option when you want a place to put related helpers.
The .NET base class library (the BCL, the set of types that ship with .NET itself) is built on a small number of interfaces that show up in almost every C# codebase. Knowing the most common ones makes a lot of standard C# code make sense.
IEnumerable<T>: Anything You Can IterateIEnumerable<T> is the contract for "this thing can be walked with foreach." Every collection in the BCL (List<T>, Dictionary<K,V>, arrays, HashSet<T>) implements it, and LINQ is built entirely on top of it.
The CartTotal method works on a List<decimal>, on a decimal[], and on anything else that implements IEnumerable<decimal>. Typing the parameter as the interface instead of a specific collection is how you write methods that work uniformly across all collection types.
IComparable<T>: Natural OrderingIComparable<T> says "instances of this type can be ordered." Sorting methods like List<T>.Sort() and Array.Sort() use it to figure out the order.
CompareTo returns a negative number if this should come before other, zero if they're equal, and a positive number if this should come after. List.Sort() repeatedly calls it on pairs and reorders the list accordingly. The interface is the bridge between the framework's sorting algorithm and your type's idea of order.
IDisposable: Releases ResourcesIDisposable is the contract for "I own something that needs explicit cleanup, like a file handle or a network connection." A class that implements it must provide a Dispose() method that frees the resource.
The using statement (and the newer using declaration) calls Dispose() automatically when the block ends, even if an exception is thrown. Any type that holds an OS resource (FileStream, SqlConnection, HttpClient) implements IDisposable precisely so callers can wrap them in using and be sure the resource gets released.
IEquatable<T>: Value-Based EqualityIEquatable<T> lets a type define its own equality logic instead of relying on the default reference equality from object. This is the contract that == for value types and HashSet<T>.Contains rely on for performance.
Two Coupon instances with the same Code count as equal because IEquatable<Coupon>.Equals says so. Without the interface, HashSet would use reference equality and treat every new Coupon { Code = "SUMMER10" } as distinct.
These four interfaces are the ones you'll trip over in your first month of writing C#. There are many more (IList<T>, IDictionary<K,V>, IFormattable, INotifyPropertyChanged), but IEnumerable<T>, IComparable<T>, IDisposable, and IEquatable<T> set the expectations for how the rest of the BCL talks about iteration, ordering, cleanup, and equality.
The discount example earlier already showed how a single method can accept any implementer of an interface. The pattern scales to collections too: a List<IShippable> can hold objects of unrelated classes that all implement IShippable, and the caller treats them uniformly.
The three classes (Order, GiftBox, Subscription) have nothing in common. They don't share a base class, they don't share fields, they don't have similar names. The only thing connecting them is that all three implement IShippable. The loop treats them as one homogeneous list.
This is where interfaces win over inheritance. With a base class, every shippable thing would need to descend from a common parent, which often forces awkward type hierarchies. With an interface, any class can opt into the contract regardless of where it sits in the class tree.
A diagram of the same idea, with the interface as the hub:
Three classes with no shared ancestry plug into the same contract. The list, the loop, and any code that takes IShippable work on all of them.
Bring the pieces together with a small order-processing flow that uses three different interfaces.
A StandardOrder implements three interfaces and so can be passed where any of those types are expected. A DigitalOrder skips IShippable because digital goods don't ship. The FinalTotal method takes the two interfaces it actually needs and ignores the rest. The same method works on both order types without knowing they exist.
The key idea: a class can implement several interfaces, and consumers ask only for the contracts they care about. That keeps method signatures focused and lets the same class participate in different parts of the system through different contracts.
A few situations trip up newcomers to interfaces.
Forgetting `public` on the implementing member. Interface members are implicitly public, and the class member that fulfills them must also be public. A private or missing-modifier method does not implement the interface, even if the name and signature match.
Trying to add fields or constructors. Interfaces describe behavior, not state. The compiler rejects fields and constructors. If you need shared state, you need an abstract class.
Adding a new member to a published interface. Once code outside your project implements your interface, adding a new member breaks every implementer. Versioning interfaces is tricky, and the modern workaround is default interface methods.
Calling interface methods on a null reference. A variable typed as IDiscountable can be null like any reference. Always check, or use a nullable interface type (IDiscountable?) when you mean to allow null.
The null-conditional operator ?. returns null instead of throwing if the receiver is null, and the null-coalescing operator ?? substitutes a fallback.