Constructor

On this page 7

A constructor is the special routine that runs automatically when a new object is created, and its job is to put that object into a valid initial state. In object-oriented programming, the constructor receives the arguments supplied at creation time, assigns them to fields, checks them against the rules of the class and acquires any resources the object needs before the rest of the program can touch it.

Unlike an ordinary method, a constructor is called once per object, has no return value of its own and is invoked through creation syntax such as new Order(42) in Java or Order(42) in Python. It is the first line of defense for class invariants, the conditions that must hold for every instance throughout its life.

How a constructor initializes an object

Object creation happens in two distinct phases: the runtime first allocates memory and sets fields to default values, then it runs the constructor to replace those defaults with meaningful data. In Java and C#, freshly allocated fields hold zero, false or null before the constructor body starts. In C++, fields of built-in types are left uninitialized unless the constructor initializes them, which is a classic source of undefined behavior.

When classes form a hierarchy, constructors run from the base class down to the derived class. A Manager that extends Employee first executes the Employee constructor, either implicitly or through an explicit call such as super(name) in Java or base(name) in C#. Only after the base part is complete does the derived constructor initialize its own fields. This ordering guarantees that inherited state is ready before subclass code depends on it.

The table below shows how several mainstream languages express the same idea, because syntax and guarantees differ more than many developers expect.

LanguageConstructor formNotable rule
JavaMethod named after the classDefault constructor added only if none is declared
C#Method named after the classStatic constructor runs once per type
C++Method named after the classMember initializer list runs before the body
Python__init__ after __new____new__ allocates, __init__ initializes
JavaScriptconstructor inside a classMust call super() before using this in subclasses
KotlinPrimary constructor in the class headerinit blocks run in declaration order
Constructor syntax in common languages

Kinds of constructors and their roles

Languages offer several constructor variants, each covering a different creation scenario. Most classes need only one or two of them, but knowing the full set helps when reading library code or designing an API that others will extend.

VariantWhat it doesTypical use
Default constructorTakes no argumentsFrameworks that create objects by reflection
Parameterized constructorAccepts values for fieldsMost domain objects
Copy constructorBuilds a duplicate of another instanceValue semantics in C++
Move constructorTransfers resources from a temporaryAvoiding expensive copies in C++
Private constructorBlocks creation from outside the classSingletons and factory methods
Static constructorInitializes type-level state onceLookup tables and caches
Common constructor variants

Constructor overloading lets a class declare several constructors with different parameter lists, which is a direct application of method overloading. To avoid duplicated logic, one constructor commonly delegates to another through this(...) in Java or : this(...) in C#, so validation lives in a single place. Python achieves similar flexibility with default argument values or alternative classmethod factories such as from_json.

A constructor is not the only way to bring an object to life, and choosing the right creation mechanism affects readability, testability and how easily the class can evolve. The comparison below places it next to its closest neighbors.

MechanismCan choose subtypeHas a descriptive nameBest fit
ConstructorNoNoSimple objects with few parameters
Static factory methodYesYesCaching, subtype selection, clear intent
BuilderYesYesMany optional parameters
Destructor or finalizerNot applicableNoReleasing resources at end of life
Initializer blockNoNoCode shared by all constructors
Constructor versus alternative creation mechanisms

The constructor’s opposite is the destructor, which runs when the object is destroyed. In C++ the pair forms the RAII pattern: the constructor acquires a file handle or lock, and the destructor releases it deterministically. Garbage-collected languages such as Java and C# have no reliable destructor, so resources are released through try-with-resources, using blocks or explicit close() calls instead.

Constructors are also not inherited in most languages. A subclass must declare its own constructors and pass the required values upward, although C++ allows inheriting constructors with a using declaration and Kotlin generates them for data classes. This rule keeps each class responsible for its own invariants and fits naturally with encapsulation.

Designing good constructors

A well-designed constructor validates input, assigns fields and stops. It should leave the object fully usable, so that no caller ever needs to remember a separate init() call. When the arguments violate the class rules, the constructor should fail fast by throwing an exception such as IllegalArgumentException or ValueError rather than producing a half-valid object.

Dependency injection relies heavily on constructors. Passing collaborators such as a repository or a clock through the constructor makes dependencies explicit, allows immutable fields declared final or readonly, and lets tests substitute mocks without special frameworks. A constructor that requires more than about four or five parameters usually signals that the class has too many responsibilities or that a builder would read better.

  • Keep the constructor free of heavy work such as network calls, file scanning or long computations.
  • Prefer immutable fields assigned once in the constructor for value objects.
  • Use a static factory when the creation logic needs a meaningful name or may return a cached instance.
  • Document which arguments may be null and reject the rest immediately.

Pitfalls and common mistakes

Most constructor bugs come from doing too much or exposing the object too early. The following table lists the problems that appear most often in code reviews, together with their causes and remedies.

SymptomCauseFix
Subclass field is null during setupOverridable method called from constructorCall only private or final methods
Random values in C++ fieldsMissing member initializationUse the initializer list for every member
Other threads see partial state`this` escapes during constructionRegister listeners after creation
Slow object creation in testsI/O inside the constructorInject data or load it lazily
Objects valid only after init()Two-phase initializationComplete setup inside the constructor
Frequent constructor problems

The overridable method trap deserves special attention. When a base constructor calls a method that a subclass overrides, the subclass version runs before the subclass constructor has initialized its own fields, so it observes default values. Java, C# and Python all allow this silently, while C++ dispatches to the base version during construction, which surprises developers in the opposite way.

A non-technical view

Think of a constructor as the setup checklist for a new item. When a hotel registers a new guest, the clerk takes the name, checks the booking, assigns a room and hands over a key card before the guest is allowed upstairs. Nobody is admitted with a missing room number.

A constructor does the same for a piece of software. Whenever a program creates something new, such as a customer record or a shopping cart, the constructor fills in the starting details and refuses to create it if something essential is wrong. The result is that every object begins life complete and ready to use.

A concrete example

A team building a payment service models money with a Money class that holds an amount in minor units and a three-letter currency code. Its constructor takes both values, rejects negative amounts and unknown currency codes, normalizes the code to upper case and stores everything in final fields. Because no setter exists, a Money object can never change after creation.

Before this design, amounts were passed around as loose numbers and strings, and a bug once mixed euro and dollar values in a refund calculation. After the change, any attempt to build Money(-500, "usd") fails immediately with a clear error at the point of creation. Invalid amounts can no longer travel deep into the system, and the refund module needs no defensive checks of its own, which shortened its code and made its unit tests simpler.