Abstract class

On this page 7

An abstract class is a class that cannot be instantiated directly and exists to be extended: it defines shared state and behavior for a family of subclasses while declaring one or more abstract methods that every concrete subclass must implement. In object-oriented programming, it sits between a fully concrete class and a pure interface, combining a partial implementation with a binding contract.

Languages such as Java, C#, C++, Python, Kotlin, TypeScript, PHP and Swift (through protocols with extensions) support the idea in different forms. The concept matters in Programming because it lets designers capture the invariant part of an algorithm in one place and leave the variable steps to subclasses, which the compiler or runtime then forces to be filled in.

How an abstract class works

An abstract class works by marking a type as incomplete so that the language refuses to create objects of it, while still allowing variables, parameters and return types of that type. In Java and C#, the abstract keyword on the class does this; in C++, a class becomes abstract as soon as it declares at least one pure virtual function (virtual void draw() = 0;). In Python, a class that inherits from ABC and has methods decorated with @abstractmethod raises a TypeError on instantiation.

The class can hold fields, constructors, concrete methods and abstract methods side by side. A constructor in an abstract class is never called with new on the class itself, but it runs when a subclass is created, which makes it the right place to initialize shared fields and validate invariants. Abstract methods have a signature and no body, and a subclass becomes concrete only when every abstract method is implemented; otherwise it must itself be declared abstract.

Calls through an abstract type use dynamic dispatch: code written against Shape calls area(), and the runtime selects the implementation of the actual object, such as Circle or Rectangle. This is the mechanism that makes polymorphism work while keeping shared code in the base.

The following table summarizes what an abstract class can and cannot contain in the main languages.

LanguageHow it is declaredCan hold stateInstantiation attempt
Javaabstract class keywordYes, any fieldsCompile-time error
C#abstract class keywordYes, any fieldsCompile-time error
C++At least one pure virtual functionYes, any membersCompile-time error
PythonInherit ABC, use @abstractmethodYes, instance attributesRuntime TypeError
TypeScriptabstract class keywordYes, typed fieldsCompile-time error only
PHPabstract class keywordYes, propertiesFatal error at runtime
Abstract class features by language

Abstract class versus interface and concrete class

An abstract class differs from an interface mainly in that it can carry instance state and constructors, and in most languages a class may extend only one abstract class but implement many interfaces. Since Java 8 and C# 8, interfaces may include default method bodies, which narrows the gap, yet interfaces still cannot hold per-object fields. A concrete class, by contrast, is complete and can be instantiated, which also means nothing forces subclasses to override anything.

The comparison below helps decide which construct fits a given role in a design.

PropertyAbstract classInterfaceConcrete class
Can be instantiatedNoNoYes
Instance fieldsYesNo (constants only)Yes
ConstructorsYes, run by subclassesNoYes
Method bodiesConcrete and abstractDefault methods in newer languagesAll concrete
Multiple inheritanceUsually singleMany per classUsually single
Main purposeShared base with required hooksCapability contractComplete implementation
Comparison with neighboring constructs

In TypeScript the abstract keyword exists only at compile time; the emitted JavaScript class can technically be instantiated, so the guarantee depends on the type checker. In Python, the check happens when an object is created, not when the subclass is defined, so a missing method surfaces only at that moment unless a test or linter catches it earlier.

Typical design patterns built on abstract classes

Abstract classes are the natural home of the template method pattern, in which a concrete, often final, method defines the fixed sequence of an algorithm and calls abstract hook methods for the steps that vary. A report generator, for example, can fix the order of loading data, formatting and writing output, while subclasses supply only the formatting. They also serve as skeletal implementations, such as Java’s AbstractList, which implement most of a large interface so that a new collection needs only a few methods.

The table lists common roles and what the abstract class contributes in each.

RoleWhat the base providesWhat subclasses supply
Template methodFixed algorithm orderIndividual steps
Skeletal implementationMost interface methodsA few primitive operations
Framework base classLifecycle and wiringApplication-specific handlers
Domain hierarchy rootShared fields and validationType-specific behavior
Test base classCommon setup and teardownSpecific test cases
Common roles of abstract classes

When to choose an abstract class

An abstract class is the right choice when several closely related types share real implementation and state, and when the base needs to guarantee an invariant through its constructor or a final method. If the types only share a capability, such as being comparable or serializable, an interface is the cleaner tool. If behavior should be swapped at runtime or combined freely, composition over inheritance usually produces a more flexible design.

SituationRecommended constructReason
Related types share fields and codeAbstract classAvoids duplication and enforces hooks
Unrelated types share one capabilityInterfaceAllows many contracts per class
Behavior varies independently on several axesCompositionAvoids a combinatorial class tree
Reusable behavior added to many classesMixin or traitAdds code without a single base
Public API that will evolve for yearsInterface plus abstract baseClients depend on the stable contract
Decision guide

A widely used compromise is to publish an interface as the public contract and provide an abstract class as an optional convenience base. Clients program against the interface, and implementers may extend the abstract base to inherit default behavior or ignore it entirely.

Pitfalls and common mistakes

The most frequent problem is the fragile base class: a change to a concrete method in the abstract base can silently break subclasses that relied on its earlier behavior. Deep hierarchies make this worse, so experienced designers keep inheritance shallow, usually one or two levels, below an abstract root.

  • Calling abstract methods from the constructor runs subclass code before the subclass fields are initialized, which causes null values in Java and C# and undefined behavior or a pure virtual call error in C++.
  • Forgetting a virtual destructor in a C++ abstract base leads to incomplete destruction when objects are deleted through a base pointer.
  • Using an abstract class only as a contract with no shared code wastes the single inheritance slot; an interface fits better.
  • Exposing protected fields couples subclasses to internal representation; protected accessor methods keep encapsulation intact.
  • Too many abstract methods burden every subclass; a base with more than a handful often signals that it should be split.

A plain-language summary

An abstract class is like a blueprint with some rooms left blank. It already shows the walls, the doors and the plumbing that every house in the development shares, but it marks a few rooms that each buyer must design. Nobody can move into the blueprint itself. A real house exists only after someone fills in the blank rooms.

In software, this means programmers write the shared parts once and the language makes sure every variant fills in the missing pieces, so nothing important is forgotten.

An example from practice

A team building a payment service supports card, bank transfer and wallet payments. They create an abstract class PaymentMethod with fields for the amount, currency and customer, a constructor that rejects negative amounts, and a final method process() that logs the attempt, calls the abstract method authorize(), then calls the abstract method capture() and records the result.

Each payment type extends the base and implements only authorize() and capture(). When a developer later adds a voucher payment and forgets capture(), the build fails at compile time instead of at checkout. Logging, validation and auditing stay identical across all four types, and the shared code lives in one place rather than being copied four times.