Skip to content
academia.sh

Lesson 01 / 23

The Purpose of TypeScript

The error class the static type layer catches, the erasure of types at compile time, and the distinction between type checking and runtime validation.

Contents

The Modules, Tooling and the Ecosystem course covered adapting source code to a target environment, and how static analysis tools work with a rule set. All these tools ask one shared question: what can be said about a program without running it? TypeScript gives the most ambitious answer — it computes which set of values every expression in the program can produce, and reports inconsistencies.

This lesson establishes three things: which error class the type layer catches, what becomes of types at runtime, and what type checking does not promise. The third is as important as the first two; the course returns to this distinction again and again in later sections.

A single concrete example runs through the whole course: a measurement record model. The model starts as an untyped JavaScript object and becomes one step safer in every topic.

Anatomy of a Silent Error

Start with a function that computes the average of measurement records. It uses nothing beyond the tools built in the JavaScript Fundamentals course:

function average(measurements) {
  let total = 0;
  for (const m of measurements) {
    total += m.value;
  }
  return total / measurements.length;
}

const records = [{ value: 21.4 }, { value: 22.1 }, { value: "23.0" }];
console.log(average(records));

Run, the output is NaN. The program does not crash, throws no error message, it produces a wrong number. The cause was established in the Type Conversion lesson of the Programming Fundamentals course: the third record’s value field is not a number but a string; when + sees one side is a string, it concatenates instead of adding. The expression 43.5 + "23.0" gives the string "43.523.0", and dividing that string by three also gives NaN.

This is a typical example of the logic-error class: the program runs, throws no error, and gives a wrong result. There is a distance between the error’s source and its symptom — the record was perhaps read from a file hours earlier, and the NaN only shows up at the end of a report.

What the Type Layer Does

Build the same program again, this time writing out explicitly which set each value belongs to:

type Measurement = { value: number };

function average(measurements: Measurement[]): number {
  let total = 0;
  for (const m of measurements) {
    total += m.value;
  }
  return total / measurements.length;
}

const records: Measurement[] = [{ value: 21.4 }, { value: 22.1 }, { value: "23.0" }];
console.log(average(records));

The notations : Measurement[] and : number are called a type annotation. When the compiler checks this file, it produces this diagnostic:

a1.ts(11,69): error TS2322: Type 'string' is not assignable to type 'number'.

The diagnostic shows not where the error happens, but where it starts: not the division that produces NaN, but the line and column where the string was placed where a number was expected. The error is caught before it turns into a symptom.

The diagnostics in this course were produced with compiler version 7. Message text can be rewritten from version to version; error codes like TS2322 are more stable. Searching for a diagnostic by code is more reliable than searching by text.

Types Are Erased at Compile Time

Now for the course’s most important distinction. TypeScript source does not run directly; it is translated to JavaScript first. What happens to type annotations in that translation?

Let the following file be saved as measurement.ts:

type Measurement = { id: string; value: number };

function average(measurements: Measurement[]): number {
  let total: number = 0;
  for (const m of measurements) {
    total += m.value;
  }
  return total / measurements.length;
}

const records: Measurement[] = [
  { id: "s-01", value: 21.4 },
  { id: "s-02", value: 22.1 },
];
console.log(average(records));

When the compiler is invoked with tsc --target es2022 measurement.ts, it writes a measurement.js file next to it:

"use strict";
function average(measurements) {
    let total = 0;
    for (const m of measurements) {
        total += m.value;
    }
    return total / measurements.length;
}
const records = [
    { id: "s-01", value: 21.4 },
    { id: "s-02", value: 22.1 },
];
console.log(average(records));

The output is the source with its type annotations stripped. The type Measurement declaration is entirely gone; the : Measurement[] and : number notations are removed. The only line the compiler added is "use strict";, and even that has nothing to do with types. Running node measurement.js gives 21.75.

This is called type erasure. Its consequence: the type layer does not change runtime behavior. The typed and untyped versions of the same program, absent a type error, do exactly the same work bit for bit. A type system is not a library, it is a layer of proof: reasoning carried out on the source code that leaves nothing behind once translation is done.

A direct consequence of this distinction is that a type error does not by itself stop emission:

record.ts(3,43): error TS2322: Type 'string' is not assignable to type 'number'.

The compilation that produces this diagnostic still writes a record.js file, and that file runs. The compiler says “this program’s types are inconsistent,” not “this program will not run.” Whether an error blocks emission is a separate configuration decision and will be covered in the Build and Run Flow lesson.

Type Checking Is Not Runtime Validation

Type erasure also explains a common false expectation. Writing a value’s type does not guarantee it will actually be that type at runtime. The guarantee only holds for the piece of code the compiler saw, and only if the information there was correct.

Information may not be correct once a measurement record comes from a file or the network:

type Measurement = { id: string; value: number };

const raw = '{"id":"s-01","value":"23.0"}';
const measurement = JSON.parse(raw) as Measurement;

console.log(typeof measurement.value);
console.log(measurement.value.toFixed(1));

This file passes type checking without error. Run, however:

string
TypeError: measurement.value.toFixed is not a function

The return type of the JSON.parse call is any; the as Measurement notation tells the compiler “treat this value as Measurement.” The compiler does not question this declaration, because it has no tool to question it with: the content of the string is only known once the program runs.

Two rules follow from this and hold throughout the course:

  1. Validation happens at the system boundary. Data coming from a file, the network, user input, or a database is accepted not by declaring its type, but by checking it field by field.
  2. A type assertion is not proof, it is a debt. The as notation silences the checker; the programmer is responsible for the correctness of what was silenced.

The tools for writing boundary validation will be built in later lessons of this course: the unknown type gives the correct starting point, and type predicates feed the result of validation back into the type system.

What the Type System Proves

A type system proves properties of the program verifiable without running it. What TypeScript proves is roughly this: every expression’s value is within the set expected of it. This proof eliminates an entire error class — access to a nonexistent field, a call with a missing argument, assignment of the wrong type, an unhandled case in a union type.

What it does not prove is just as clear:

  • Value range. The declaration value: number does not say the temperature is within a physically possible range.
  • Termination and correctness. That a function computes the average correctly cannot be expressed at the type level; this is the domain of the partial correctness and termination concepts from the Algorithms course.
  • The shape of the outside world. As seen in the example above, what lies beyond the boundary is not checked.

TypeScript’s type system is also deliberately not fully sound: any, type assertions, and a few language features leave escape hatches that pierce the guarantee. This is a design goal, not an oversight — the language has to be addable, gradually, on top of existing JavaScript code. The escape hatches are covered one by one in the any, unknown, and never lesson.

The Course’s Shared Example

The same model will be worked on throughout the course. The starting point is this object, carrying no guarantees at all:

const measurement = {
  id: "s-01",
  sensor: "temperature",
  value: 21.4,
  unit: "C",
  time: 1706000000000,
};

Nothing is known about this object yet: the unit field could hold any string at all, the value field could be a string, the sensor field could be deleted. Every topic closes one of these uncertainties — first the fields’ types, then the relations between fields, then the states the record passes through, and finally how the model gets published at the project boundary.

Summary

  • The static type layer computes every expression’s value set without running the program, and reports inconsistencies at compile time.
  • Diagnostic messages can change with version; error codes like TS2322 are a more stable reference.
  • Types are erased at compile time: the compiled JavaScript output is the source with its type annotations removed, and runtime behavior is unchanged.
  • A type error does not by itself stop emission; the compiler still writes the JavaScript file even when it produces a diagnostic.
  • Declaring a type is not runtime validation; data from a system boundary has to be checked field by field.
  • The type system does not prove value range, algorithm correctness, or the shape of the outside world.

Next Step

What the type layer does is now established; next comes this layer’s vocabulary. To write the measurement record’s fields, the basic types available have to be known first: primitive types, array and tuple notations, and the enum construct that names a fixed set of values. The next lesson builds these and writes the model’s first typed version.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close