The main difference between an interface and a class in TypeScript is that an interface only describes the shape of an object, whereas a class describes the shape and also contains the code that creates and runs the object. The compiler deletes every interface from the JavaScript output, so an interface costs nothing at runtime and cannot be used with new or instanceof. A class stays in the output as a real JavaScript class, so we can create instances of it and check them at runtime.
We use an interface for the shape of data, such as an API response, and a class when the objects need their own code, such as a shopping cart that recalculates its total after each change.
For a Java developer, the split looks familiar, but the rules are looser. TypeScript compares types by their structure, not by their declared names, so a plain object can match an interface and even the type of a class without any implements clause. TypeScript interfaces also cannot hold method bodies, and since there is no equivalent of Java’s default methods, we use an abstract class for that role.
The following example declares one interface and one class that implements it, compiled with TypeScript 7.0.2 and run on Node.js 22. It creates an object of each kind and runs the two runtime checks that only the class supports.
// 1. Interface: shape only, no code, removed by the compiler
interface Shape {
name: string;
area(): number;
}
// 2. Class: shape plus code, kept in the JavaScript output
class Square implements Shape {
name = "square";
side: number;
constructor(side: number) { this.side = side; }
area(): number { return this.side * this.side; }
}
// 3. Objects: new for a class, an object literal for an interface
const square = new Square(4);
const tile: Shape = { name: "tile", area: () => 1 };
const a1 = square.area(); // a1 = 16
const a2 = tile.area(); // a2 = 1
// 4. Runtime checks work only with the class
const isSquare = square instanceof Square; // isSquare = true
const kind = typeof Square; // kind = "function"
Notice that the object tile needs no class at all, whereas typeof Square shows that the class is a real function at runtime.
Next, we compare the two on method bodies, object creation, inheritance and access modifiers, and we look at the compiled JavaScript to see what remains after the build. The last section gives a short rule for choosing between them.
1. The Same Shape Written as an Interface and as a Class
An interface lists property names, property types and method signatures, whereas a class lists the same things and adds the constructor that sets the properties and the bodies of the methods. Here UserShape is the contract and User is one implementation of it.
interface UserShape {
name: string;
age: number;
greet(): string;
}
class User implements UserShape {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return "Hi, I am " + this.name;
}
}
const lokesh = new User("Lokesh", 37);
const text = lokesh.greet(); // text = "Hi, I am Lokesh"
The compiled JavaScript shows the most important difference. The UserShape interface and all type annotations are gone, whereas the class stays, because JavaScript has had classes of its own since ES2015.
class User {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return "Hi, I am " + this.name;
}
}
const lokesh = new User("Lokesh", 37);
const text = lokesh.greet(); // text = "Hi, I am Lokesh"
Because the interface has no JavaScript output, every check it provides happens at compile time. Data that enters the program at runtime, for example from JSON.parse(), is not checked against an interface unless we write that check ourselves.
2. Method Bodies: Interfaces, Classes and Abstract Classes
An interface method has a signature and no body, whereas a class method must have a body. When we want some methods with shared code and some left for subclasses, we use an abstract class, which can have both kinds of methods. For example, every shape on a drawing canvas describes itself the same way, but each shape computes its own area. Java developers know this split already, but TypeScript interfaces have no default methods, so shared code always goes into a class.
// Abstract class: some methods with code, some without
abstract class Shape {
abstract area(): number;
describe(): string {
return "area " + this.area();
}
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
area(): number {
return this.width * this.height;
}
}
const text = new Rectangle(2, 3).describe(); // text = "area 6"
An abstract class cannot be instantiated, so new Shape() fails with error TS2511: Cannot create an instance of an abstract class. Unlike an interface, an abstract class exists at runtime, and a subclass can extend only one of them. Subclasses can override any of its methods or add new ones.
3. Creating Objects and Structural Typing
A class creates objects with new. An interface has no constructor, so we create a plain object that has the required properties.
One behavior often confuses Java developers. A class name used as a type means “an object with the public members of this class”, not “an instance of this class”. So we can assign a plain object literal to a variable of a class type, as long as it has the same public members. For example, a unit test can pass a plain object where a function expects a Square.
class Square {
side: number;
constructor(side: number) { this.side = side; }
area(): number { return this.side * this.side; }
}
// 1. A plain object matches the public shape of the class
const fake: Square = { side: 2, area: () => 4 };
const area = fake.area(); // area = 4
// 2. But it is not an instance of the class
const isSquare = fake instanceof Square; // isSquare = false
The compiler accepts fake, but instanceof returns false, because the object was not created by the Square constructor. Code that relies on instanceof can therefore fail on values that type-check. A class with a private member or a #private field blocks plain objects, because a plain object cannot have that member. For example, the compiler reports Property ‘#balance’ is missing in type ‘{ deposit: (n: number) => void; }’ but required in type ‘Account’.
4. Inheritance: extends and implements
Both constructs support inheritance, but with different limits.
- A class extends at most one class, the same rule as in Java.
- An interface can extend several interfaces.
- A class can implement several interfaces.
interface Named {
name: string;
}
interface Aged {
age: number;
}
// 1. An interface can extend several interfaces
interface Person extends Named, Aged {}
interface Printable {
print(): string;
}
class Base {
greet(): string { return "Hi"; }
}
// 2. A class extends one class and implements several interfaces
class User extends Base implements Person, Printable {
name: string;
age: number;
constructor(name: string, age: number) {
super();
this.name = name;
this.age = age;
}
print(): string { return this.name + ", " + this.age; }
}
const raj = new User("Raj", 35);
const hello = raj.greet(); // hello = "Hi", inherited from Base
const printed = raj.print(); // printed = "Raj, 35"
Writing class C extends A, B is rejected with error TS1174: Classes can only extend a single class. With extends, a class inherits code, so User gets greet() from Base without writing it. With implements, a class inherits nothing and must declare every member itself. TypeScript also allows an interface to extend a class, which Java does not.
5. Constructors, Access Modifiers and Static Members
Several features exist only in classes, because they only make sense for code that runs. For example, a User class can count its instances in a static field and keep the password in a field that no outside code can read.
- A constructor
- The access modifiers public, protected and private
- Private fields with a # name, such as #password, that stay private at runtime
- Static members that belong to the class instead of each object
class User {
static count = 0; // belongs to the class
#password: string; // private at runtime too
protected age: number;
readonly name: string;
constructor(name: string, age: number, password: string) {
this.name = name;
this.age = age;
this.#password = password;
User.count++;
}
checkPassword(input: string): boolean {
return input === this.#password;
}
}
const john = new User("John", 40, "secret");
new User("Raj", 35, "hello");
const ok = john.checkPassword("secret"); // ok = true
const count = User.count; // count = 2
The private keyword is checked only by the compiler and disappears from the output, while a #password field is enforced by the JavaScript engine itself. In an interface, every member is public, so the compiler rejects modifiers there with error TS1070.
error TS1070: 'public' modifier cannot appear on a type member.
error TS1070: 'static' modifier cannot appear on a type member.
The readonly modifier is the exception. It describes the shape, so both interfaces and classes accept it.
6. Runtime Behavior and instanceof
At runtime, a class is a function (typeof Square is “function”) with a prototype, and every object created with new is linked to that prototype, which is what instanceof checks. An interface has no runtime value, so value instanceof Shape does not compile when Shape is an interface, and the compiler reports error TS2693: ‘Shape’ only refers to a type, but is being used as a value here.
When we need to check an interface type at runtime, we write a type guard function that tests the properties, or we use the in operator. For example, a webhook handler checks that the request body has the fields of an OrderEvent before it uses the body.
7. Summary Table and When to Use Each
The “Exists at runtime” row explains most of the other rows. Anything that needs code to run, such as a constructor or an instanceof check, belongs to classes.
| Feature | Interface | Class |
|---|---|---|
| Purpose | Describes a shape | Describes a shape and implements it |
| Exists at runtime | No, removed by the compiler | Yes, a JavaScript class |
| Method bodies | No | Yes (abstract methods have none) |
| Create objects with new | No | Yes, unless abstract |
| Constructor | No | Yes |
| public, private, protected, static | No | Yes |
| readonly and optional members | Yes | Yes |
| Inheritance | Extends many interfaces | Extends one class, implements many interfaces |
| Declaration merging | Yes | No, a second class with the same name is an error |
| instanceof check | No | Yes |
We use an interface for data shapes and contracts, such as API responses, function parameters, configuration and anything that crosses a module boundary. We use a class when the objects need behavior tied to their state, a constructor that validates input, private data, or a runtime type for instanceof. In many programs, both appear together, with an interface for the contract and one or more classes that implement it. The interface also lets tests pass a plain object in place of the real class.
8. Example Code on GitHub
The difference-between-interface-and-class project contains each snippet as a function and an index.ts that runs them. It compiles with TypeScript 7.0.2 and runs on Node.js 22 or newer.
npm install
npm start
After the build, the compiled files in dist/src contain the classes but none of the interfaces, as section 1 explained.
9. Conclusion
An interface in TypeScript is a compile-time description of a shape, and a class is a runtime construct that also carries a shape. Interfaces can extend several parents and merge declarations, and they cost nothing in the output, but they have no code and no instanceof check. Classes have constructors, method bodies, access modifiers, static members and a runtime identity, but extend only one parent. Since TypeScript compares shapes, even a class type accepts plain objects unless the class has private members.
10. References
The handbook pages explain both constructs in full, and the MDN page describes the JavaScript classes that TypeScript classes compile to.
- TypeScript Handbook: Object Types
- TypeScript Handbook: Classes
- TypeScript Handbook: Type Compatibility
- MDN: Classes
- MDN: Private properties
Happy Learning !!