← Fundamentals
Guided lesson 5 questions · 6 languages

Constructors and creating values

Literals, constructors, and the one place where a new value can be checked.

A new value gets its first state where it is created: from a literal, a constructor, or a function that builds it. When every new value of a type is created in one place, that place can make sure each one starts out valid.

Before you begin

Why this matters

Most wrong values are wrong from the moment they are created: a property that was never set, or two arguments in the wrong order. Knowing how a value is created lets you find those mistakes, and stop them in one place.

What this helps you reason about

  • Predict what an object made with new holds, property by property.
  • Spot a constructor argument that is never stored, and two strings passed in the wrong order.
  • Write one function that refuses to create a value that breaks the type’s rule.

Noor joins the Design team for 30 hours a week. A program that records this needs a value with her name, her team, and her hours. This lesson follows where such a value gets its first state, and what goes wrong there.

Already know this? The short version
  • An object literal must give every property its type requires (Property … is missing); a class property needs a starting value or an assignment in the constructor (strictPropertyInitialization).
  • A property with a starting value, such as hours = 37, satisfies the compiler even when the constructor never stores the hours it was given; a parameter without public is only a parameter.
  • Arguments are matched by position, so two strings can swap without an error; one object parameter matches by name.
  • Put a type’s rule in one factory function or constructor and create every value there; a structural type still accepts any object literal of the right shape.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
object
A value that groups named properties, written with braces: { value: 3 } is an object with one property, value.
class
A declaration that describes a kind of object: its properties and a constructor. new Employee(…) creates an object of the class Employee.
constructor
The function inside a class that runs when new creates an object. It gives the new object’s properties their first values; inside it, this is the new object.
parameter property
A constructor parameter marked public, such as public name: string. TypeScript declares a property with that name and stores the argument in it.
factory function
An ordinary function that creates and returns a new value, such as newEmployee. It can check its arguments first and refuse to create the value.
invariant
A rule that every value of a type must follow, such as “weekly hours are from 1 to 40”.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / First state

Where does a new value get its first state?

A new value gets its first state where it is created. An object literal, such as { name: "Noor", team: "Design", hours: 30 }, writes every property directly.

A class describes a kind of object: its properties and a constructor. The constructor is the function inside the class that runs when new creates an object. Inside it, this is the new object, so this.name = name stores the parameter name in the property name.

A constructor parameter marked public is a parameter property: TypeScript declares a property with that name and stores the argument in it. The class below is the short form of one with three properties and three assignments.

main.ts
class Employee {
  constructor(
    public name: string,
    public team: string,
    public hours: number,
  ) {}
}

const noor = new Employee("Noor", "Design", 30);

A literal writes the properties directly; a constructor runs code that sets them.

Coming from Python: The constructor is like __init__, and this is like self, but this is never written as a parameter.

Coming from Java: public before a constructor parameter declares and stores the property in one step; a class has one constructor, with no overloads that have different bodies.

Precisely: what new does

new creates an empty object linked to the class, sets the properties that have starting values, runs the constructor body, and gives back the object. The parameter properties are stored before the rest of the constructor body runs. Classes with extends add a call to the parent class’s constructor, which comes first.

Experiment

Follow new into the constructor

new creates an empty object and runs the constructor with this set to it. Each this.x = x line copies one parameter into one property of the new object.

Why it matters Every object made with new Employee(…) went through the constructor, so the constructor decides what a new Employee holds.

Predict what the run prints. Then change new Employee("Noor", "Design", 30) to new Employee("Noor", "Design") and predict again.

class Employee {
  name: string;
  team: string;
  hours: number;

  constructor(name: string, team: string, hours: number) {
    this.name = name;
    this.team = team;
    this.hours = hours;
  }
}

const noor = new Employee("Noor", "Design", 30);
console.log(noor.name, noor.team, noor.hours);
What to look for

Step into the new line. The first stop inside the call is in a frame named <instance_members_initializer>: Node sets up the class’s properties there before the constructor body runs. Then the Employee frame lists this first, then the parameters name "Noor", team "Design", and hours 30. this starts as {name: undefined, team: undefined, hours: undefined}, and each assignment fills one property. noor shows not initialized yet until the call is over; then it shows {name: "Noor", team: "Design", hours: 30}, the object that this was. The run prints Noor Design 30. After the edit, nothing runs, and TypeScript reports Line 13: Expected 3 arguments, but got 2.

Expected output

Noor Design 30

02 / Left out

What happens to a field you do not set?

An object literal must give every property its type requires. Leaving hours out is a compile error: TypeScript reports Property 'hours' is missing in type '{ name: string; team: string; }' but required in type 'Employee'.

A class property needs a value too. A property declared without one must be assigned in the constructor, or TypeScript reports Property 'hours' has no initializer and is not definitely assigned in the constructor.

A property can also have a starting value, as in hours = 37. Then it always has a value, and the compiler has nothing to report, even when the constructor receives other hours and never stores them.

main.ts
type Employee = { name: string; team: string; hours: number };

// Does not compile: Property 'hours' is missing …
const noor: Employee = { name: "Noor", team: "Design" };

TypeScript refuses a missing property, unless the property has a starting value.

Precisely: optional properties

A property written hours?: number may be left out, and then reading it gives undefined. The check on class properties is the compiler option strictPropertyInitialization, part of strict; writing hours!: number switches it off for one property.

Experiment

Pass hours to a constructor that does not keep it

public before a parameter declares a property and stores the argument in it. hours has no public, so it is only a parameter. The property hours comes from the line hours = 37, and the compiler sees that it always has a value.

Why it matters TypeScript reports a property that might never be assigned, but not a parameter that is never stored. Check that every parameter of a constructor ends up somewhere.

Predict noor.hours. Then add the line this.hours = hours; between the braces of the constructor, and predict again.

class Employee {
  hours = 37;

  constructor(
    public name: string,
    public team: string,
    hours: number,
  ) {}
}

const noor = new Employee("Noor", "Design", 30);
console.log(noor.name, noor.team, noor.hours);
What to look for

Step into the new line. The first stop is on hours = 37, in the <instance_members_initializer> frame: the property gets its starting value there. Then the stepper stops on public name and public team, which store those two arguments. The Employee frame shows the parameter hours 30 all the time, but nothing stores it: this shows hours: 37. noor then shows {name: "Noor", team: "Design", hours: 37}, and the run prints Noor Design 37. After the edit, the stepper also stops on the new line, noor shows hours: 30, and the run prints Noor Design 30.

Expected output

Noor Design 37

03 / Order

Which argument goes into which field?

Arguments are matched by position: the first argument goes into the first parameter. new Employee("Design", "Noor", 30) compiles, because both are strings, and creates an employee named Design.

The properties of an object literal are matched by name, so they can be written in any order. A factory function, an ordinary function that creates and returns a new value, can take one object parameter and get the same safety.

main.ts
function newEmployee(fields: {
  name: string;
  team: string;
  hours: number;
}): Employee {
  return { name: fields.name, team: fields.team, hours: fields.hours };
}

const noor = newEmployee({ team: "Design", name: "Noor", hours: 30 });

Arguments are matched by position; properties in an object literal are matched by name.

Coming from Python: TypeScript has no keyword arguments; an object parameter is the usual way to name what is passed.

04 / Valid

How can creating a value keep it valid?

A type often has a rule that every value must follow, such as “weekly hours are from 1 to 40”. Such a rule is called an invariant. The place that creates values is where an invariant can be checked. newEmployee below refuses to create an employee who breaks the rule and returns null, the value for “nothing”, instead. The return type Employee | null says that the result is one or the other, so the caller must check it.

This protects every value only when every value is made by newEmployee. Any object with the right properties fits the type Employee, so code that writes its own object literal skips the check.

main.ts
function newEmployee(
  name: string,
  team: string,
  hours: number,
): Employee | null {
  if (hours < 1 || hours > 40) {
    return null;
  }
  return { name: name, team: team, hours: hours };
}

Check a type’s rule where its values are created, and create every value there.

Coming from Java: An object type is structural: an object literal of the right shape is an Employee without running any constructor.

Precisely: a private constructor

A class with a private constructor and a static function that creates objects, such as static create(…): Employee | null, makes that function the only way to get an Employee from outside the class. private is checked by the compiler only; JavaScript’s # fields are private while the program runs too.

Experiment

Refuse an employee with too many hours

newEmployee checks the rule before it builds the object. When the rule fails, it returns null instead of an Employee, and the return type makes the caller deal with both.

Why it matters When every value of a type is created in one place, a check in that place holds for every value. A value made anywhere else skips it.

Predict both results. Then change the 50 to 40 and predict again.

type Employee = { name: string; team: string; hours: number };

// Gives an Employee, or null when hours is not 1 to 40.
function newEmployee(
  name: string,
  team: string,
  hours: number,
): Employee | null {
  if (hours < 1 || hours > 40) {
    return null;
  }
  return { name: name, team: team, hours: hours };
}

const noor = newEmployee("Noor", "Design", 30);
const pavel = newEmployee("Pavel", "Sales", 50);
console.log(noor);
console.log(pavel);
What to look for

Step into the first call. hours shows 30, the if is false, and the stepper stops on the return line. noor then shows {name: "Noor", team: "Design", hours: 30}. In the second call hours shows 50, the stepper stops on return null;, and pavel shows null. The run prints { name: 'Noor', team: 'Design', hours: 30 }, then null. After the edit, 40 is allowed, and the second line is { name: 'Pavel', team: 'Sales', hours: 40 }.

Expected output

{ name: 'Noor', team: 'Design', hours: 30 }
null

05 / Try it

Can you explain a fresh example?

Decide where each property gets its value. The next lesson, Stack and heap, shows where new values are stored while a program runs. Its link is at the end of the page.

main.ts
class Employee {
  hours = 37;

  constructor(
    public name: string,
    public team: string,
  ) {}
}

const ines = new Employee("Ines", "Sales");
const cy = new Employee("Legal", "Cy");
console.log(ines.hours, cy.name);

What is printed?

Reveal the reasoning

37 Legal. The constructor has no hours parameter, so every Employee gets the starting value 37. For cy, the arguments are in the wrong order. Both are strings, so the compiler accepts them, and the first one, "Legal", goes into name.

Practice tickets

  1. CTOR-101 · Every new repair request shows priority 0 → Free
  2. CTOR-102 · Imported staff names appear back to front → Sign in · Pro
  3. CTOR-103 · The phone booth can be booked after closing time → Sign in · Pro

Keep exploring

Stack and heap →

Primary references