cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Classes in TypeScript

Type a class's fields, constructor, methods, and getters, see why every field needs a starting value, and shorten constructors with parameter properties.

By the end, you can
  • 01
    Declare typed fieldsWrite each field and its type in the class body, and read what TypeScript works out from a starting value.
  • 02
    Give every field a valueFix strictPropertyInitialization errors with a starting value, the constructor, ?, or (carefully) !.
  • 03
    Write shorter, typed classesTurn constructor parameters into fields with parameter properties, and type methods, getters, and setters.

A class that knows its types

A shop page makes a Product object for every item it sells. Here is a JavaScript class that does it. A class is a recipe for objects: new Product(…) runs its constructor and gives back a new object (Class basics covers this). One price comes from a form, so it's text:

A product class in plain JavaScriptPop out in the code editor (opens in a new tab)JavaScript
class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}

const tea = new Product("Tea", "3");
console.log(tea.price + 1);

Lines 2 to 5 are the constructor. It stores the two arguments on the new object as this.name and this.price. Line 8 makes a tea that costs "3", in quotes, so the price is text. Line 9 adds 1 and prints 31, because + joins text instead of adding. Nothing warns you.

Here is the same class in TypeScript. Two lines are new, and the constructor's parameters have types:

The same class with types (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Product {  name: string;  price: number;   constructor(name: string, price: number) {    this.name = name;    this.price = price;  }} const tea = new Product("Tea", "3");console.log(tea.price + 1);

Lines 2 and 3 are field declarations. They list the properties every Product has, and the type of each: name is a string (text) and price is a number. Line 5 types the constructor's parameters the same way. Lines 6 and 7 store them, as before.

Line 11 gets an error, on purpose, because it's the bug:

  • Argument of type 'string' is not assignable to parameter of type 'number' means "you're passing text where the constructor wants a number". An argument is a value you pass in a call, and new Product(…) is a call to the constructor.
  • The code still runs and prints 31. TypeScript only warns you; it removes the types and the browser runs the rest.

Try it: type a different price for line 11 and watch the list under the code.

Playground: what new Product gets as a price
A product made with newPop out in the code editor (opens in a new tab)TypeScript
class Product {  name: string;  price: number;   constructor(name: string, price: number) {    this.name = name;    this.price = price;  }} const tea = new Product("Tea", "3");console.log(tea.price + 1);
The price is"3"
What happens
TypeScript1 error: line 11
When it runsprints 31
Try it yourself

Line 11 passes the text "3" where the constructor wants a number. TypeScript says Argument of type 'string' is not assignable to parameter of type 'number'. When it runs, "3" + 1 joins the two and prints 31.

The real TypeScript compiler checks the code each time you type. Only line 11 changes. Reset puts the text "3" back.

With 3 or Number("3"), the price is a number, so the error goes away and the code prints 4. An empty box and 3, 2 get a different error: the constructor takes exactly two arguments, a name and a price, and TypeScript counts them.

Left, a window named Product holds two cards: name: string and price: number. An arrow labelled new Product("Tea", 3) leads to a cart named tea holding name: "Tea" and price: 3.the classProductname: stringprice: numberwhat every Product hasnew Product("Tea", 3)one objectname: "Tea"price: 3tea
The class says every Product has a name that is text and a price that is a number. Each object new makes has those two fields, and TypeScript checks the values that go in.
Real-life analogyA printed order form

A shop prints one order form with labeled boxes: Name, and Price (numbers only). Every customer fills in a fresh copy. Before the clerk takes a copy, she checks each box. If someone wrote letters in the price box, she hands it back to be fixed, before anything is ordered.

In real life: The blank form, printed once
In TypeScript: The class, class Product { … }
In real life: Each box on the form, with its label: Name, Price (numbers only)
In TypeScript: Each field declaration: name: string, price: number
In real life: A filled-in copy of the form
In TypeScript: One object made by new Product("Tea", 3)
In real life: The clerk who hands back a form with letters in the price box
In TypeScript: TypeScript marking "3" where a number goes
The main idea

A TypeScript class is a JavaScript class with types. You declare each field and its type in the class body, and type the parameters of the constructor and the methods. TypeScript checks every new call and every use of the objects. Then the types are removed, and the class runs as plain JavaScript.

Declaring fields

In JavaScript, a constructor can add any property it likes with this.something = …. TypeScript wants to know an object's properties before the constructor runs, so it can check every use of them. Here is what happens if you skip the declaration:

A property the class never declared (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Cart {  constructor() {    this.total = 0;  }}

Line 3 stores 0 in this.total. TypeScript says Property 'total' does not exist on type 'Cart': it looked at the class body for a total and found none. The fix is to declare it. A field declaration can have a type, a starting value, or both:

Three ways to declare a fieldPop out in the code editor (opens in a new tab)TypeScript
class Cart {  items: string[] = [];  total = 0;  coupon?: string;} const cart = new Cart();const total = cart.total;//    ^?const coupon = cart.coupon;//    ^?console.log(total, coupon);
  • Line 2 has a type and a starting value: items is a list of strings (string[]) and starts empty.
  • Line 3 has only a starting value. TypeScript works out the type from it, just as it does for a variable. The query on line 9 shows const total: number.
  • Line 4 has a question mark: coupon?: string makes the field optional. It may hold a string or nothing at all, so the query on line 11 shows string | undefined. The | means "or".

Line 12 prints 0 undefined: the total starts at 0 and nobody has set a coupon yet. A field line is still JavaScript after the types are removed: total = 0; gives every new cart its own total, as a JavaScript field does.

A field with neither a type nor a starting value gives TypeScript nothing to work with:

A field with no type and no value (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Cart {  items;  total = 0;}

Line 2 declares items and says nothing else. TypeScript reports Member 'items' implicitly has an 'any' type. The type any means "anything at all, and don't check it". With TypeScript's strict setting, which this lesson's examples use, TypeScript won't pick any for you silently. Write items: string[] = []; to say what it holds.

Every field needs a starting value

A field declared as email: string promises that email is always a string. TypeScript checks that promise: each field whose type doesn't allow undefined must get a value, either where it's declared or in the constructor. This check is called strictPropertyInitialization. It's a compiler setting, and the strict setting turns it on.

A profile that forgets its email (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class UserProfile {  name: string;  email: string;   constructor(name: string) {    this.name = name;  }} const profile = new UserProfile("Asha");console.log(profile.email.toUpperCase());

Lines 2 and 3 declare name and email. The constructor sets name on line 6, and nothing sets email. TypeScript marks line 3: Property 'email' has no initializer and is not definitely assigned in the constructor. In plain words: this field has no starting value (an initializer), and the constructor doesn't always set it. When the code runs, line 11 calls toUpperCase() on undefined and stops with a TypeError.

Left, an editor window lists name: string, marked set on line 6, and email: string, marked never set. Right, when the code runs, the profile holds name: Asha and email: undefined.what TypeScript checkseditorname: stringset on line 6email: stringnever setwhen it runsprofilename: "Asha"email: undefined
TypeScript reads the constructor and sees that only name gets a value. When the code runs, the profile's email is undefined, which is what the error on line 3 warned about.

There are four common ways to write line 3. Press each one and watch the errors and the output. The code prints the email with a template literal, so nothing throws this time:

Playground: four ways to write the email field
A user profile with an email fieldPop out in the code editor (opens in a new tab)TypeScript
class UserProfile {  name: string;  email: string;   constructor(name: string) {    this.name = name;  }} const profile = new UserProfile("Asha");console.log(`Email: ${profile.email}`);
Console outputEmail: undefined
What happens
Line 3email: string;
TypeScript1 error: line 3
Try it yourself

Line 3 says email is always a string, but nothing gives it a value. TypeScript says Property 'email' has no initializer and is not definitely assigned in the constructor. When it runs, email is undefined, and the page prints Email: undefined.

TypeScript checks the code for real, and the output is what this code prints. Each button changes only line 3. Reset goes back to No starting value.

A starting value fixes the bug. The ? is honest too: it changes the type, so any code that uses email as text has to check for undefined first. The ! after a field name is called a definite assignment assertion. It removes the error without giving a value, so use it only when something TypeScript can't see really does set the field, like a library that fills it in.

The table puts the four side by side. Read each row as: what you write, what TypeScript then knows, and what the field holds.

Four ways to give a field its value
When you writeWhat TypeScript knowsWhat the field holds at first
email = "not set";email is a string, worked out from the value.The text "not set", from the very start.
email: string; and this.email = … in the constructoremail is a string. TypeScript checked that every path through the constructor sets it.Whatever the constructor stored.
email?: string;email is string | undefined, so you must check before using it as text.undefined, until something sets it.
email!: string;email is a string, because you promised. TypeScript stops checking.undefined, until something sets it. If nothing does, the promise was wrong.

The constructor can set a field too. TypeScript follows every path through it:

The constructor sets the email on every pathPop out in the code editor (opens in a new tab)TypeScript
class UserProfile {  name: string;  email: string;   constructor(name: string, email?: string) {    this.name = name;    if (email) {      this.email = email;    } else {      this.email = "not set";    }  }} const profile = new UserProfile("Asha");console.log(profile.email);

Line 5 takes an optional email parameter. Lines 7 to 11 set this.email in both branches of the if: the given email, or "not set". Every path sets it, so there's no error. Line 15 passes no email, and line 16 prints not set. Delete the else branch in Edit & run and the error on line 3 comes back.

TypeScript only looks inside the constructor itself, not inside the methods it calls:

A value set in a method doesn't count (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class UserProfile {  name: string;   constructor() {    this.reset();  }   reset() {    this.name = "Guest";  }}

Line 5 calls reset(), and line 9 sets name. You can see that it works, but TypeScript still marks line 2. It doesn't follow method calls, because a subclass (a class that extends this one, as Class inheritance shows) can replace reset() with a version that doesn't set name. Set the field in the constructor, give it a starting value, or, if you're sure, write name!: string.

Projects that turn the check off set strictPropertyInitialization to false in their tsconfig.json, the file that holds a project's compiler settings. This example sets it, and the error disappears while the bug stays:

The same profile with the check turned offPop out in the code editor (opens in a new tab)TypeScript
class UserProfile {  name: string;  email: string;   constructor(name: string) {    this.name = name;  }} const profile = new UserProfile("Asha");console.log(profile.email);

It's the profile from the start of this section, with no errors at all. Line 11 prints undefined, even though line 3 says email is a string. Keep the check on.

Parameter properties: fields from the constructor

Look back at the overview's class. Each field is written three times: once in the class body (name: string), once as a parameter (name: string), and once in the copy (this.name = name). TypeScript has a shortcut for this, called a parameter property: put public in front of a constructor parameter, and it becomes a field too.

A product with parameter propertiesPop out in the code editor (opens in a new tab)TypeScript
class Product {  constructor(public name: string, public price: number) {}} const tea = new Product("Tea", 3);const price = tea.price;//    ^?console.log(tea.name, price);

Line 2 is the whole class body. public name: string means three things: the constructor takes a name, every Product has a name field, and the argument is stored in it. The braces {} at the end are the constructor's empty body. The query on line 7 shows const price: number, and line 8 prints Tea 3.

public means anyone can use the field, which is already the default for fields. The next lesson covers private, protected, and readonly, which work here as well. Unlike the type annotations, a parameter property writes JavaScript. Here is what TypeScript gives the browser:

Left, a file product.ts with constructor(public name: string, public price: number). An arrow labelled TypeScript writes leads to a file product.js with the fields name and price, and this.name = name and this.price = price.product.tsconstructor(public name: string,public price: number) {}you write thisTypeScriptwritesproduct.jsname;price;this.name = name;this.price = price;the browser runs this
You write public in front of each parameter. The JavaScript gets a field for each one and a line in the constructor that copies the argument into it.
What the browser gets: the fields and the copiesPop out in the code editor (opens in a new tab)JavaScript
class Product {
  name;
  price;
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}
const tea = new Product("Tea", 3);
const price = tea.price;
console.log(tea.name, price);

Lines 2 and 3 are the fields, and lines 5 and 6 copy the arguments into them. That's the same class the long way, written for you. Line 11 prints Tea 3.

Each parameter needs its own public. Press the buttons to change line 2:

Playground: which parameters become fields
A product built from parameter propertiesPop out in the code editor (opens in a new tab)TypeScript
class Product {  constructor(public name: string, public price: number) {}} const tea = new Product("Tea", 3);console.log(`${tea.name}: $${tea.price}`);
Console outputTea: $3
What happens
TypeScriptNo errors
Try it yourself

Both parameters have public, so both become fields. tea.name and tea.price exist, and line 6 prints Tea: $3.

TypeScript checks the code for real, and the output is what this code prints. Each button changes only line 2. Reset goes back to public on both.

A parameter with no modifier is a plain parameter: the constructor gets the value, and the object doesn't keep it. With public on name only, tea.price doesn't exist, and line 6 gets an error. With No modifiers, neither does tea.name, so line 6 has two errors.

The table compares the long way and the shortcut. Read each row as a question, then each way's answer.

The long way and parameter properties
QuestionThe long wayParameter properties
Where the fields are listedIn the class body: name: string; and price: number;.In the constructor's brackets: public name: string.
What the constructor body doesCopies each parameter: this.name = name;.Nothing. The body can be empty: {}.
What the browser runsThe same class, without the types.The same class as the long way: TypeScript writes the fields and the copies for you.
Tools that only remove typesRun it.Can't run it. erasableSyntaxOnly reports it.
Tools that only remove types can't run them

Most TypeScript features vanish when the types are removed. Parameter properties don't: something has to write the fields and the copies. Node.js can run a .ts file by deleting its types, and it stops with an error at a parameter property. Projects run that way turn on the erasableSyntaxOnly setting (TypeScript 5.8 and later), which marks them:

erasableSyntaxOnly reports a parameter property (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Product {  constructor(public name: string) {}}

This example sets erasableSyntaxOnly, so line 2 gets This syntax is not allowed when 'erasableSyntaxOnly' is enabled. The fix is the long way: a field in the class body and a copy in the constructor. Without that setting, as in the rest of this lesson, parameter properties are fine.

Methods, getters, and setters

A method is a function that belongs to the class. You type its parameters like any function's, and you can write the type of value it gives back, its return type, after the brackets:

Two typed methodsPop out in the code editor (opens in a new tab)TypeScript
class Product {  constructor(public name: string, public price: number) {}   label(): string {    return `${this.name}: $${this.price}`;  }   discounted(percent: number) {    return this.price - (this.price * percent) / 100;  }} const tea = new Product("Tea", 4);const sale = tea.discounted(25);//    ^?console.log(tea.label(), sale);
  • Line 4 says label returns a string. If line 5 returned something else, TypeScript would mark it.
  • Inside a method, this is the object the method was called on, so TypeScript knows this.price is a number.
  • Line 8 types percent and leaves the return type out. TypeScript works it out from line 9: the query on line 15 shows const sale: number.
  • Line 16 prints Tea: $4 3: the label, then 4 minus 25% of 4.

A getter is a method that you read like a field: get total() runs each time code reads line.total (Getters & setters covers how they work in JavaScript). TypeScript adds one rule: a getter with no setter is read-only.

A total you can read but not set (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class CartLine {  constructor(public name: string, public price: number, public quantity: number) {}   get total() {    return this.price * this.quantity;  }} const line = new CartLine("Tea", 3, 2);line.total = 10;console.log(line.total);

Line 2 uses parameter properties for the name, price, and quantity. Lines 4 to 6 are the getter: it multiplies the price by the quantity. Line 10 tries to set line.total, and TypeScript says Cannot assign to 'total' because it is a read-only property. JavaScript has nowhere to store the 10. In this lesson's editor, line 10 does nothing and line 11 prints 6. In strict mode, which every JavaScript module uses, line 10 throws a TypeError instead.

A cart named line holds price: 3 and quantity: 2. Reading line.total runs the getter get total(), which works out 3 times 2, and gives 6.line.totalprice: 3quantity: 2lineget total()works out 3 * 26the value
line.total looks like a field, but reading it runs get total(), which multiplies the price by the quantity each time. With no setter, TypeScript treats total as read-only.

A setter, written set, runs when code assigns to the property. It's a good place to clean up a value before you store it:

A setter that keeps the quantity sensible (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class QuantityField {  count = 1;   get quantity() {    return this.count;  }   set quantity(value) {    this.count = Math.max(1, Math.round(value));  }} const field = new QuantityField();field.quantity = 2.6;console.log(field.quantity);field.quantity = -4;console.log(field.quantity);field.quantity = "5";
  • Line 2 stores the real number in a field called count.
  • Lines 4 to 6 are the getter. It returns count, so TypeScript works out that quantity is a number.
  • Line 8's value has no type written. When a setter's parameter has no type, TypeScript takes the getter's return type: point at value and you see (parameter) value: number.
  • Line 9 rounds the value and keeps it at least 1. Line 14 sets 2.6, and line 15 prints 3. Line 16 sets -4, and line 17 prints 1.
  • Line 18 assigns the text "5". The setter takes a number, so TypeScript marks it, on purpose: Type 'string' is not assignable to type 'number'.

Any code can still change count directly and skip the setter's checks. The next lesson shows how private stops that.

A shopping cart class

Here is everything together: a cart for a shop page, with a typed list of items, a parameter property, a method, and two getters. Line 1 is a type alias: type CartItem = … gives a name to the shape of one item.

A typed shopping cartPop out in the code editor (opens in a new tab)TypeScript
type CartItem = { name: string; price: number; quantity: number }; class ShoppingCart {  items: CartItem[] = [];   constructor(public owner: string) {}   add(name: string, price: number, quantity = 1) {    this.items.push({ name, price, quantity });  }   get count() {    return this.items.length;  }   get total() {    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);  }} const cart = new ShoppingCart("Asha");cart.add("Tea", 3, 2);cart.add("Cake", 4);console.log(`${cart.owner} has ${cart.count} items: $${cart.total}`);
  • Line 4 declares items, a list of CartItem objects that starts empty.
  • Line 6 makes owner a field with a parameter property.
  • Line 8 types add's parameters. quantity = 1 is a default value, so TypeScript works out that it's a number and that callers may leave it out.
  • Lines 12 to 18 are two getters. total adds up price times quantity with reduce, which combines a list into one value (map, filter & reduce covers it).
  • Line 24 prints Asha has 2 items: $10: two teas at 3 and one cake at 4.

Because the class has types, every mistake in using it is marked. Here are four uses; three get errors on purpose:

Using the cart the wrong way (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type CartItem = { name: string; price: number; quantity: number }; class ShoppingCart {  items: CartItem[] = [];   constructor(public owner: string) {}   add(name: string, price: number, quantity = 1) {    this.items.push({ name, price, quantity });  }} const cart = new ShoppingCart("Asha");cart.add("Tea", "3");cart.add("Cake");cart.items = [];cart.owner = 7;
  • Line 14 passes the price as text: Argument of type 'string' is not assignable to parameter of type 'number'.
  • Line 15 leaves out the price: Expected 2-3 arguments, but got 1. The quantity is optional, so add takes two or three.
  • Line 16 empties the list. That's allowed: an empty list is still a list of CartItem.
  • Line 17 sets the owner to a number: Type 'number' is not assignable to type 'string'.

Now sort each line. Will TypeScript report an error, or is it fine?

Error or fine?
  • class Cart { total: number; }
  • class Cart { total = 0; }
  • class Cart { total?: number; }
  • class Cart { constructor() { this.total = 0; } }
  • class Tag { constructor(public label: string) {} }
  • class Line { get total() { return 6; } } new Line().total = 7;
  • class Cart { total!: number; }
  • class Cart { items; }
Try it yourself
0 of 8 correct

Each card is one class, checked on its own with this lesson's settings. Place it in the group TypeScript would put it in.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "The ! gives a field a value." No. It only tells TypeScript to stop checking that field. The field is still undefined until some code sets it.
  • "A parameter needs public to be used inside the constructor." No. Any parameter can be used in the constructor. public also makes it a field on the object.
  • "TypeScript checks the values passed to new while the page runs." No. The types are removed first. A price that arrives as text from a server gets into a Product without a warning.
  • "Setting a field in a method the constructor calls is enough." No. TypeScript only looks inside the constructor itself, so the field still gets an error.
  • "A getter needs a setter." No. A getter on its own is fine. It makes a property you can read but not set.
  • "Field declarations are only types and disappear." No. The type after the colon disappears, but the field itself stays in the JavaScript, like total = 0;.

Practice exercises

Each exercise is TypeScript. Press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upFind the field TypeScript marks

An order class with three fields. Which line will TypeScript mark?

Starter codePop out in the code editor (opens in a new tab)TypeScript
class Order {
  id: number;
  customer: string;
  paid = false;

  constructor(id: number) {
    this.id = id;
  }
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upRead a field's type

    What type does TypeScript give size? Guess first. Then point at size, or press Edit & run and add a // ^? line under it.

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    class Settings {  theme = "dark";  fontSize = 16;} const size = new Settings().fontSize;

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeShorten a constructor

      Rewrite the class with parameter properties, so it does the same thing in fewer lines. What does it print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      class Product {  name: string;  price: number;   constructor(name: string, price: number) {    this.name = name;    this.price = price;  }} const cake = new Product("Cake", 4);console.log(`${cake.name} costs ${cake.price}`);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeGive the avatar a default

        Every new profile should show the picture /images/default.png until the user uploads one. Fix line 3. What does the fixed code print?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        class UserProfile {  name: string;  avatarUrl: string;   constructor(name: string) {    this.name = name;  }} const asha = new UserProfile("Asha");console.log(asha.avatarUrl);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeAdd a total getter

          Line 13 reads cart.total, which doesn't exist yet. Add a getter that adds up the prices. What does it print?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          class Cart {  prices: number[] = [];   add(price: number) {    this.prices.push(price);  }} const cart = new Cart();cart.add(3);cart.add(3);cart.add(4);console.log(cart.total);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeClean up a search box

            A search box should store what the user typed without extra spaces and in lowercase. Line 10 gets an error. Add a setter that cleans the text. What does the fixed code print?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            class SearchBox {  text = "";   get query() {    return this.text;  }} const box = new SearchBox();box.query = "  Green Tea  ";console.log(box.query);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, think about two moments: what TypeScript says before the code runs, and what the code does when it runs. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The Classes in TypeScript quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does strictPropertyInitialization check?

                Choose an answer to see the explanation.

              2. Question 2 of 7Which line does TypeScript flag in this toast message class?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Toast {
                  message: string;
                  seconds: number;
                
                  constructor(message: string) {
                    this.message = message;
                  }
                }

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this badge code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Badge {
                  constructor(public label: string, count: number) {}
                }
                
                const badge = new Badge("New", 3);
                console.log(Object.keys(badge).join(", "));

                Choose an answer to see the explanation.

              4. Question 4 of 7This profile uses !. What does it print, and does TypeScript complain?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Profile {
                  name!: string;
                }
                
                console.log(new Profile().name);

                Choose an answer to see the explanation.

              5. Question 5 of 7Which line does TypeScript flag in this timer?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Timer {
                  seconds = 90;
                
                  get minutes() {
                    return this.seconds / 60;
                  }
                }
                
                const timer = new Timer();
                timer.minutes = 2;

                Choose an answer to see the explanation.

              6. Question 6 of 7Why does TypeScript flag line 6 of this cart?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Cart {
                  count = 0;
                }
                
                const cart = new Cart();
                cart.count = "2";

                Choose an answer to see the explanation.

              7. Question 7 of 7Why does erasableSyntaxOnly report a parameter property?

                Choose an answer to see the explanation.

              Key takeaways

              • Declare every field in the class body, with a type, a starting value, or both: items: string[] = [].
              • TypeScript checks each new call against the constructor's parameters, and each use of a field against its type.
              • With strictPropertyInitialization, a field whose type doesn't allow undefined needs a value by the end of the constructor. ? allows undefined; ! turns the check off for one field.
              • A parameter property, like constructor(public name: string), declares a field and stores the argument in one go.
              • Methods are typed like functions. A getter without a setter is read-only, and a setter's parameter takes the getter's type.

              Remember the one-liner.
              Declare each field with its type, give it a value by the end of the constructor, and let TypeScript check every object you make.

              Coming next: public, private, protected & #private. It shows who can use each field and method, and how TypeScript's private differs from JavaScript's #private.

              CompleteFrontend Clear concepts. Working examples.