cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Generic classes

Write TypeScript classes like Stack<T> and Cache<T> that keep their item type, see why static members can't use T, and add generic methods.

By the end, you can
  • 01
    Write a generic classGive a class a type parameter, like class Stack<T>, and use T in its fields, constructor, and methods.
  • 02
    Fill in a class's TFill in T with new Stack<string>() or from the constructor's arguments, and spot when it becomes unknown.
  • 03
    Use static members and generic methodsExplain why static members can't use T, and give a method its own type parameter, like map<U>.

A stack that keeps its item type

A text editor's undo button needs to remember your changes, newest first. A stack does that: a list where you add and remove items at the same end, like a pile of plates. push puts an item on top, and pop takes the top one off. Here is a stack class written for text (the Classes in TypeScript lesson covers fields and methods):

A stack that only takes text (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class TextStack {  private items: string[] = [];  push(item: string) {    this.items.push(item);  }  pop(): string | undefined {    return this.items.pop();  }} const edits = new TextStack();edits.push("Hello");const zoom = new TextStack();zoom.push(150);console.log(edits.pop(), zoom.pop());

Line 2 keeps the items in a private string[]. Line 3's push takes a string, and line 6's pop gives back the top string, or undefined when the stack is empty. Lines 11 and 12 make a stack of edits and push "Hello". Line 13 makes a second stack for zoom levels, and line 14 pushes 150. TypeScript marks it, on purpose:

  • Argument of type 'number' is not assignable to parameter of type 'string' means "you passed a number where this method wants text".

Run it anyway and line 15 prints Hello 150: the code works for any item, and only the types say text. You could copy the class as NumberStack, and again for every kind of item. A generic class writes it once:

One Stack class for any item typePop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  private items: T[] = [];  push(item: T) {    this.items.push(item);  }  pop(): T | undefined {    return this.items.pop();  }} const edits = new Stack<string>();edits.push("Hello");const zoom = new Stack<number>();zoom.push(150);const lastZoom = zoom.pop();//    ^?console.log(edits.pop(), lastZoom);

Line 1 has the new part: <T> after the class name. As in the Generic types lesson, T is a type parameter, a blank for a type. Line 2 keeps a T[], line 3 takes a T, and line 6 gives one back.

Line 11 writes new Stack<string>(): the type argument string fills in T for this stack. Line 13 makes a Stack<number>, so pushing 150 on line 14 is fine. The query on line 16 shows const lastZoom: number | undefined. There are no errors, and line 17 prints Hello 150.

A card, class Stack<T>, with arrows to two crates. The crate edits is a Stack<string>, where T is string, holding Hello. The crate zoom is a Stack<number>, where T is number, holding 150.class Stack<T>one classHelloeditsStack<string>T = string150zoomStack<number>T = number
One class, two stacks. Line 11 makes edits with T filled in as string, and line 13 makes zoom with T as number. Each stack only takes, and gives back, its own kind of item.
Real-life analogyOne shelf design, labelled when it's put up

A shop orders one shelf design for its storeroom. Each time a shelf goes up, it gets a label: "cups", "plates", "bowls". Staff only put cups on the cups shelf, so anyone who takes something off it knows it's a cup without looking.

In real life: One shelf design in the catalogue
In TypeScript: One class: class Stack<T>
In real life: Putting up a shelf and labelling it "cups"
In TypeScript: Making a stack: new Stack<string>()
In real life: Staff can only put cups on the cups shelf
In TypeScript: edits.push(150) is an error
In real life: Whatever you take off the cups shelf is a cup
In TypeScript: edits.pop() is a string, or undefined
The main idea

A generic class has a type parameter after its name, like class Stack<T>. Its fields, constructor, and methods use T. Each object you make fills in T, like new Stack<string>(), and keeps that type for as long as it exists.

Type parameters on a class

You can write the type argument after new, as in the overview. When the constructor takes a T, TypeScript can also fill in T from the value you pass. A drop-down list on a shop page is a good example: each choice has a label to show and a value to use.

A Choice class that fills in T from its valuePop out in the code editor (opens in a new tab)TypeScript
class Choice<T> {  constructor(public label: string, public value: T) {}} const size = new Choice("Large", "L");//    ^?const quantity = new Choice("Two", 2);//    ^?console.log(size.value, quantity.value + 1);

Line 2 is a constructor with two parameter properties: public label: string and public value: T become fields straight from the constructor's arguments (see Classes in TypeScript). Line 5 passes "L" as the value, so TypeScript fills in T with string, and line 6 shows const size: Choice<string>. Line 7 passes 2, and line 8 shows Choice<number>. Line 9 prints L 3.

A type argument you write wins, and the constructor's arguments are checked against it:

A written type argument and a wrong value (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Choice<T> {  constructor(public label: string, public value: T) {}} const size = new Choice<string>("Large", "L");const quantity = new Choice<number>("Two", "2");console.log(size.value, quantity.value);

Line 5 writes <string>, the same type TypeScript would fill in. Line 6 writes <number> and passes the text "2", so TypeScript marks it: Argument of type 'string' is not assignable to parameter of type 'number'. Line 7 prints L 2 anyway.

Our Stack's constructor takes nothing, so only a type argument can fill in its T. Here an undo history pushes "Hello" and shows the last change with showMessage, which needs text. Press the buttons to make the history three ways on line 15:

Playground: how the undo history is made
An undo history built on StackPop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  private items: T[] = [];  push(item: T) {    this.items.push(item);  }  pop(): T | undefined {    return this.items.pop();  }} function showMessage(text: string | undefined) {  console.log(text ?? "Nothing to undo");} const history = new Stack<string>();history.push("Hello");const last = history.pop();//    ^?showMessage(last);
last's typestring | undefined
What happens
TypeScriptNo errors
When it runsprints Hello
Try it yourself

new Stack<string>() fills in T with string. Pushing "Hello" fits, pop gives back string | undefined, and showMessage accepts it. No errors.

TypeScript checks the code for real. Each button changes only line 15. Every version prints Hello when it runs, because the types are gone by then. Reset goes back to <string>.

With <string>, everything fits. With No type argument, nothing tells TypeScript what T is, so it becomes unknown, a type that means "this could be anything". The stack takes any push, and what comes out can't be used as text. TypeScript doesn't look at the later push("Hello") to decide. With <number>, line 16's text doesn't fit.

A typed Stack and Cache

A cache keeps server replies so the page doesn't ask for them twice. Each reply is stored under a key, like the address it came from. A generic class can use other generic types inside it, like a Map:

A Cache class built on a MapPop out in the code editor (opens in a new tab)TypeScript
type Product = { name: string; price: number }; class Cache<T> {  private entries = new Map<string, T>();  set(key: string, value: T) {    this.entries.set(key, value);  }  get(key: string): T | undefined {    return this.entries.get(key);  }} const productCache = new Cache<Product[]>();productCache.set("/products", [{ name: "Tea", price: 3 }]);const cached = productCache.get("/products");//    ^?console.log(cached?.length, productCache.get("/sale"));

Line 3 declares Cache<T>. Line 4 keeps the replies in a Map<string, T>: text keys, and values of whatever type T is (the Map and Set lesson covers Map). Line 5's set takes a T, and line 8's get gives back a T, or undefined for a key it doesn't have.

Line 13 makes a cache for lists of products, Cache<Product[]>. Line 14 stores one list, and the query on line 16 shows const cached: Product[] | undefined. Line 17 prints 1 undefined: one product, and nothing stored under "/sale". The ?. reads length only when cached isn't undefined.

Now change what the cache stores. Type a type argument for line 13, and watch line 14 and the answer on line 16:

Playground: what the cache stores
A cache for server repliesPop out in the code editor (opens in a new tab)TypeScript
type Product = { name: string; price: number }; class Cache<T> {  private entries = new Map<string, T>();  set(key: string, value: T) {    this.entries.set(key, value);  }  get(key: string): T | undefined {    return this.entries.get(key);  }} const productCache = new Cache<Product[]>();productCache.set("/products", [{ name: "Tea", price: 3 }]);const cached = productCache.get("/products");//    ^?console.log(cached?.length, productCache.get("/sale"));
T is filled in withProduct[]
What happens
cached's typeProduct[] | undefined
TypeScriptNo errors
Try it yourself

Cache<Product[]> stores lists of products. Line 14 stores one, so there are no errors, and cached is Product[] | undefined.

The code is checked by the real TypeScript compiler each time you type. Only line 13 changes, and it always prints 1 undefined when it runs. Reset puts Product[] back.

Product without the brackets is one product, so a list doesn't fit. string doesn't fit either. Array<Product> is the same as Product[]. any accepts the list, but then TypeScript stops checking what comes out.

A generic class can also promise to match a generic interface. Write the interface's type argument after implements (the implements and abstract classes lesson covers implements):

A generic class that implements a generic interfacePop out in the code editor (opens in a new tab)TypeScript
interface Store<T> {  get(key: string): T | undefined;  set(key: string, value: T): void;} class MemoryStore<T> implements Store<T> {  private entries = new Map<string, T>();  get(key: string) {    return this.entries.get(key);  }  set(key: string, value: T) {    this.entries.set(key, value);  }} const preferences: Store<string> = new MemoryStore<string>();preferences.set("theme", "dark");console.log(preferences.get("theme"));

Lines 1 to 4 describe a Store<T>: anything with a get and a set for values of type T. Line 6 says MemoryStore<T> implements Store<T>, passing its own T on, so a MemoryStore<string> is a Store<string>. Line 16 makes a store for a page's preferences as text, line 17 stores the theme, and line 18 prints dark. Code that takes a Store<string> works with this class, or with any other class that stores text the same way.

Static members can't use T

A static member belongs to the class itself, not to each object (the Static members and class types lesson covers them). There is only one Stack class, however many kinds of stack you make:

One static counter for every kind of stackPop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  static created = 0;  private items: T[] = [];  constructor() {    Stack.created += 1;  }} new Stack<string>();new Stack<number>();console.log(Stack.created);

Line 2 makes a static created counter, and line 5 adds 1 each time a stack is made. Line 9 makes a Stack<string> and line 10 a Stack<number>. Line 11 prints 2: both stacks counted on the same static field. Stack<string> and Stack<number> are two types, but one class.

A window titled Stack, the class itself, holds two cards: static created: 2, and static from<Item>. Under it: one class, no T. Arrows from it lead to two cards, new Stack<string>() with T = string and new Stack<number>() with T = number.Stackstatic created: 2static from<Item>()the class itself: one, no Tnew Stack<string>()T = stringnew Stack<number>()T = number
Every stack is made by the same Stack class, and static members live on that one class. It has no T of its own, so a static member can't use T. That's why created counts both stacks, and why from brings its own type parameter, Item.

So a static member can't use T: it belongs to the class, and the class serves every T at once. A common case is a static factory, a method on the class that makes objects for you, like Stack.from(["S", "M"]). Press the buttons to write line 9 three ways:

Playground: three ways to write from
A stack made from a listPop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  constructor(private items: T[] = []) {}  push(item: T) {    this.items.push(item);  }  pop(): T | undefined {    return this.items.pop();  }  static from(items: T[]): Stack<T> {    return new Stack(items);  }} const sizes = Stack.from(["S", "M"]);//    ^?console.log(sizes.pop());
sizes's typeStack<T>
What happens
TypeScript2 errors: line 9
When it runsprints M
Try it yourself

from is static, so it belongs to the class itself, and the class has no T. TypeScript marks both uses of T on line 9: Static members cannot reference class type parameters. Line 15 shows Stack<T>: a blank nothing fills in.

TypeScript checks the code for real, and the output is what the code does when it runs. Each button changes only line 9. Reset goes back to static, using T.

static, using T gets Static members cannot reference class type parameters twice, once for each T. static from<Item> fixes it: the method gets its own type parameter, filled in for each call. Not static makes from a method of each stack, so the class has no from at all.

The table below shows where a class's T can go. Read each row as: this part of the class, can it use T, and why.

Where a class's T can go
Where in the classCan it use the class's T?Why
An instance field, like items: T[]Yes.Each stack has its own items, made with its own T.
A constructor parameter, like value: TYes.new fills in T, from a type argument or from the arguments you pass.
An instance method, like push(item: T)Yes.You call it on one stack, and that stack has a T.
A static field or method, like static fromNo.It belongs to the class itself, shared by every T. Give a static method its own type parameter instead.

Generic methods: a type parameter for one method

The class's T is filled in once, when the object is made. Sometimes a method needs a type that changes with each call. Then the method gets a type parameter of its own, written after its name, like the Generic functions lesson's functions:

A map method with its own type parameterPop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  constructor(private items: T[] = []) {}  peek(): T | undefined {    return this.items.at(-1);  }  map<U>(change: (item: T) => U): Stack<U> {    return new Stack(this.items.map(change));  }} const prices = new Stack([3, 4]);//    ^?const labels = prices.map((price) => "$" + price);//    ^?console.log(labels.peek());

Line 2's constructor takes the starting items. Line 3's peek gives back the top item without removing it: at(-1) reads the last one. Line 6 declares map<U>. It takes a change function that turns a T into a U, and gives back a Stack<U>. Line 7 uses the array's own map (see map, filter, and reduce).

Line 11 passes [3, 4], so the query on line 12 shows const prices: Stack<number>. Line 13 calls map with a callback that gives back text, so this call's U is string, and line 14 shows const labels: Stack<string>. Line 15 prints $4, the top label.

Left: the crate prices, a Stack<number> holding 3 and 4, where T is number, fixed when it was made. An arrow labelled map((price) => "$" + price), U = string, leads to the crate labels, a Stack<string> holding $3 and $4.Stack<number>34pricesT = number,fixed when it was made.map(…)U = stringfilled in by this callStack<string>$3$4labelswhat map gives back
prices got T = number when line 11 made it. Line 13's call to map fills in the method's own U from the callback: it gives back text, so U is string, and you get a Stack<string>.

A method uses the class's T without declaring it. Writing <T> on the method again makes a new T, unrelated to the class's:

A method that declares T again (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  private items: T[] = [];  push<T>(item: T) {    this.items.push(item);  }} const tags = new Stack<string>();tags.push(3);

Line 3 writes push<T>, so inside push, T means the method's own type parameter. Line 4 marks it: Argument of type 'T' is not assignable to parameter of type 'T'. In plain words: these are two different blanks that happen to share a name, and the method's T might not be the stack's. Worse, line 9 pushes 3 into a Stack<string> with no error, because push no longer uses the stack's T. Remove <T> from line 3, and both problems are gone.

The table below compares the two kinds of type parameter. Read each row as a question, then each answer.

The class's T and a method's own type parameter
QuestionThe class's TA method's own type parameter
When is it filled in?Once, when the object is made: new Stack([3, 4]).Each time you call the method: prices.map(…).
Where can you use it?In every field and instance method of the class.Only inside that one method.
What fills it in?A type argument, or the constructor's arguments.A type argument, or that call's arguments, like the callback.

Undo for a settings panel

A settings panel lets the user change the theme and the font size, and undo their last change. The undo part doesn't care what the settings are, so one generic class can handle it, and the same class can undo text in a box:

A History class for settings and textPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: "light" | "dark"; fontSize: number }; class History<T> {  private past: T[] = [];  constructor(private current: T) {}  get value(): T {    return this.current;  }  change(next: T) {    this.past.push(this.current);    this.current = next;  }  undo() {    const previous = this.past.pop();    if (previous !== undefined) this.current = previous;  }} const settings = new History<Settings>({ theme: "light", fontSize: 16 });settings.change({ theme: "dark", fontSize: 16 });settings.change({ ...settings.value, fontSize: 18 });settings.undo();const now = settings.value;//    ^?console.log(now.theme, now.fontSize); const draft = new History("Hi");//    ^?draft.change("Hi Asha");draft.undo();console.log(draft.value);

Line 1 describes the settings. Lines 3 to 17 are History<T>:

  • Line 4 keeps the past values in a T[], used as a stack. Line 5 takes the starting value as a private current field.
  • Line 6 is a getter (see Getters and setters): settings.value reads the current value.
  • Lines 9 to 12 push the current value onto past, then switch to the new one.
  • Lines 13 to 16 pop the last value off past and go back to it, if there is one.

Line 19 makes a History<Settings>. Line 20 switches to the dark theme. Line 21 copies the current settings with the spread ... (see Spread and rest) and changes the font size to 18. Line 22 undoes that, so the query on line 24 shows const now: Settings and line 25 prints dark 16.

Line 27 makes a history for a text box. The constructor's argument is text, so line 28 shows const draft: History<string>. Lines 29 and 30 change it and undo, and line 31 prints Hi.

For an object, the type argument matters. Without it, TypeScript fills in T from the starting value, and that can be too loose:

A typo with and without the type argument (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: "light" | "dark"; fontSize: number }; class History<T> {  constructor(private current: T) {}  change(next: T) {    this.current = next;  }} const settings = new History<Settings>({ theme: "light", fontSize: 16 });settings.change({ theme: "blue", fontSize: 16 });const guessed = new History({ theme: "light", fontSize: 16 });//    ^?guessed.change({ theme: "blue", fontSize: 16 });

Line 10 writes <Settings>, so line 11's typo is caught: Type '"blue"' is not assignable to type '"light" | "dark"' means "the theme must be one of these two words". Line 12 leaves the type argument out. TypeScript fills in T from the object, and the query on line 13 shows theme: string: any text. So line 14's "blue" gets no error. When the starting value doesn't say everything, write the type argument.

Error or fine?
  • class Box<T> { constructor(public value: T) {} } new Box<number>("3");
  • class Box<T> { constructor(public value: T) {} } new Box("3").value.toUpperCase();
  • class Box<T> { static empty: T[] = []; }
  • class Box<T> { static count = 0; value?: T; }
  • class Box<T> { items: T[] = []; } new Box<string>().items.push(3);
  • class Box<T> { items: T[] = []; } new Box().items.push(3);
  • class Box<T> { constructor(public value: T) {} } const label: Box<string> = new Box(3);
  • class Box<T> { value?: T; } const saved: unknown = new Box(); console.log(saved instanceof Box);
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own.

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

Common mix-ups

Type parameters are removed before the code runs, like every type. So while the code runs, a stack doesn't know its T:

instanceof checks the class, not TPop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  constructor(public items: T[] = []) {}} const saved: unknown = new Stack([3, 4]);if (saved instanceof Stack) {  console.log(saved.items);  //                ^?}

Line 5 stores a stack of numbers as unknown, as if it came from somewhere TypeScript can't see. Line 6 checks it with instanceof (the in and instanceof lesson covers it). Inside, TypeScript knows it's a Stack, but not which kind: the query on line 8 shows (property) Stack<any>.items: any[]. Line 7 prints [ 3, 4 ]. Writing saved instanceof Stack<number> is an error, because there's no T to check while the code runs.

The table below shows what is left of a generic class when the code runs. Read each row as: what you write, what TypeScript checks, and what runs.

A generic class before and while the code runs
What you writeWhat TypeScript checksWhat runs in the browser
class Stack<T> { … }Every use of T inside the class.class Stack { … }: one class, with no T.
new Stack<string>()That everything pushed is text.new Stack(). The type argument is removed.
saved instanceof StackIt narrows saved to Stack<any>.It checks that Stack made the object. It can't see T.
  • "Stack<string> and Stack<number> are two classes." No. They're two types made from one class. A static field, like created, is shared by both.
  • "TypeScript works out T from what I push later." No. T is filled in when the object is made. new Stack() with nothing to go on is a Stack<unknown>.
  • "A static method can use the class's T." No. Static members belong to the one class, which serves every T. Give the static method its own type parameter, like from<Item>.
  • "A method must declare <T> to use T." No. Every instance method can use the class's T. Declaring it again makes a new, unrelated T.
  • "instanceof can check Stack<string>." No. It only checks the class. The type argument is gone when the code runs.
  • "A class that holds any kind of item needs any inside." No. T lets it hold any kind of item and still check each object's own kind.

Practice exercises

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

Exercise 1 · Warm-upRead the type pop gives back

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

Starter codePop out in the code editor (opens in a new tab)TypeScript
class Stack<T> {  private items: T[] = [];  push(item: T) {    this.items.push(item);  }  pop(): T | undefined {    return this.items.pop();  }} const orders = new Stack<string>();orders.push("Tea");const next = orders.pop();

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

    Exercise 2 · Warm-upFind the push TypeScript marks

    A stack of sizes, and one push with a mistake. Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    class Stack<T> {
      private items: T[] = [];
      push(item: T) {
        this.items.push(item);
      }
    }
    
    const sizes = new Stack<string>();
    sizes.push("S");
    sizes.push("M");
    sizes.push(42);

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

      Exercise 3 · PracticeMake the recent list generic

      RecentList keeps the 3 most recent items, newest first, but only takes text, so TypeScript marks line 14. Make it generic. What does line 15 print then?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      class RecentList {  private items: string[] = [];  add(item: string) {    this.items = [item, ...this.items].slice(0, 3);  }  latest(): string | undefined {    return this.items[0];  }} const viewed = new RecentList();viewed.add("Tea");const prices = new RecentList();prices.add(4);console.log(viewed.latest(), prices.latest());

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

        Exercise 4 · PracticeFill in the cache's type

        TypeScript marks line 17. Fix it with a type argument on line 15. What goes in the angle brackets?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        class Cache<T> {  private entries = new Map<string, T>();  set(key: string, value: T) {    this.entries.set(key, value);  }  get(key: string): T | undefined {    return this.entries.get(key);  }} function showPrice(price: number | undefined) {  console.log(price === undefined ? "No price" : "$" + price);} const prices = new Cache();prices.set("Tea", 3);showPrice(prices.get("Tea"));

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

          Exercise 5 · ChallengeFix the static factory

          Stack.of makes a stack from the items you pass, with a rest parameter ...items (see Spread and rest). TypeScript marks line 6. Fix it. What does the code print?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          class Stack<T> {  constructor(private items: T[] = []) {}  pop(): T | undefined {    return this.items.pop();  }  static of(...items: T[]): Stack<T> {    return new Stack(items);  }} const sizes = Stack.of("S", "M", "L");console.log(sizes.pop());

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

            Exercise 6 · ChallengeWrite a generic method

            getOr gives back a stored value, or a fallback when there isn't one. TypeScript marks fallback on line 6 because it has no type. Give the method a type parameter. What type does size get?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            class Cache<T> {  private entries = new Map<string, T>();  set(key: string, value: T) {    this.entries.set(key, value);  }  getOr(key: string, fallback) {    const found = this.entries.get(key);    return found === undefined ? fallback : found;  }} const sizes = new Cache<string>();sizes.set("shirt", "M");const size = sizes.getOr("shoes", null);console.log(size);

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

              Check your understanding

              For each question, ask when each type parameter is filled in: the class's T when the object is made, or a method's own one at each call. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The generic classes quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Where do you write a class's type parameter?

                Choose an answer to see the explanation.

              2. Question 2 of 7What type does giftWrap get?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Box<T> {
                  constructor(public value: T) {}
                }
                
                const giftWrap = new Box(true);

                Choose an answer to see the explanation.

              3. Question 3 of 7What type does last get from this stack made without a type argument?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Stack<T> {
                  private items: T[] = [];
                  push(item: T) {
                    this.items.push(item);
                  }
                  pop(): T | undefined {
                    return this.items.pop();
                  }
                }
                
                const history = new Stack();
                history.push("Hello");
                const last = history.pop();

                Choose an answer to see the explanation.

              4. Question 4 of 7Which line does TypeScript flag in this class with static members?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Stack<T> {
                  private items: T[] = [];
                  static created = 0;
                  static empty(): T[] {
                    return [];
                  }
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7What type does inStock get from this map call?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Stack<T> {
                  constructor(private items: T[] = []) {}
                  map<U>(change: (item: T) => U): Stack<U> {
                    return new Stack(this.items.map(change));
                  }
                }
                
                const inStock = new Stack([3, 0]).map((count) => count > 0);

                Choose an answer to see the explanation.

              6. Question 6 of 7What does this count of created stacks print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                class Stack<T> {
                  static created = 0;
                  constructor() {
                    Stack.created += 1;
                  }
                }
                
                new Stack<string>();
                new Stack<number>();
                new Stack<string>();
                console.log(Stack.created);

                Choose an answer to see the explanation.

              7. Question 7 of 7What can saved instanceof Stack tell you while the code runs?

                Choose an answer to see the explanation.

              Key takeaways

              • A generic class has a type parameter after its name, like class Stack<T>, used in its fields, constructor, and instance methods.
              • Each object fills in T when it's made: from a type argument, new Stack<string>(), or from the constructor's arguments.
              • With nothing to fill it in, T is unknown. Later calls don't change it.
              • Static members belong to the one class, shared by every T, so they can't use T. Give a static method its own type parameter.
              • A method can have its own type parameter, like map<U>, filled in at each call. Don't declare the class's T again on a method.
              • While the code runs, there's no T: instanceof checks only the class.

              Remember the one-liner.
              A generic class is one class whose objects each fill in its blank, and keep that type for life.

              Coming next: Generics in practice. It builds a typed fetch helper and a typed event emitter, and shows how to read the generic types in a library.

              CompleteFrontend Clear concepts. Working examples.