Type Systems
Type Systems
Introduction
In this lesson, you'll learn about type systems in TypeScript. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with type systems.
TypeScript has its own approach to type systems, which we'll explore step by step.
The TypeScript Way
Let's see how TypeScript handles this concept. Here's a typical example:
// TypeScript - structural typing
let name: string = "Alice";
let age: number = 30; // all numbers are number
let active: boolean = true;
let items: string[] = []; // or Array<string>
interface Printable {
print(): void;
}
// No "implements" needed if shape matches!
const doc = {
print() { console.log("doc"); }
};
// doc is compatible with Printable automaticallyComparing to Java
Here's how you might have written similar code in Java:
// Java - nominal typing
String name = "Alice";
int age = 30;
boolean active = true;
List<String> items = new ArrayList<>();
interface Printable {
void print();
}
class Doc implements Printable { // must declare!
public void print() { System.out.println("doc"); }
}You may be used to different syntax or behavior.
TypeScript uses structural typing — if an object has the right shape, it's compatible, no explicit declaration needed
You may be used to different syntax or behavior.
Java uses nominal typing — a class must explicitly say 'implements Interface' to be compatible
You may be used to different syntax or behavior.
Java has int, long, float, double; TypeScript has a single number type for all numeric values
You may be used to different syntax or behavior.
Java String (capital S) vs TypeScript string (lowercase)
You may be used to different syntax or behavior.
TypeScript generics use similar <T> syntax: Array<string> or string[]
Step-by-Step Breakdown
1. Structural vs Nominal Typing
This is the most important conceptual difference. TypeScript checks the shape of an object, not its declared type name.
interface Flyable { void fly(); }
class Bird implements Flyable { // must declare
public void fly() { }
}interface Flyable { fly(): void; }
// Works WITHOUT "implements Flyable"!
const bird = {
fly() { console.log("flap"); },
sing() { console.log("tweet"); }
};
function takeOff(f: Flyable) { f.fly(); }
takeOff(bird); // ✓ — bird has fly(), so it's Flyable2. Number is One Type
Java has separate integer and floating-point types. TypeScript has a single number type covering all numeric values.
int count = 10;
double price = 9.99;
long bigNum = 9_000_000_000L;let count: number = 10;
let price: number = 9.99;
let bigNum: number = 9_000_000_000;
// For very large integers, use bigint:
let huge: bigint = 9_000_000_000_000n;3. Union and Literal Types
TypeScript can express types Java cannot — a variable can be one of several specific types or values.
// Union type — string OR number
let id: string | number = "abc-123";
id = 42; // also valid
// Literal type — only these exact values
type Direction = "north" | "south" | "east" | "west";
let dir: Direction = "north";
// dir = "up"; // compile error!
// Optional — string or undefined
let nickname: string | undefined;4. Type Inference
TypeScript infers types from initial values, so you rarely need to annotate every variable.
String name = "Alice"; // must annotate
int count = 0;let name = "Alice"; // inferred: string
let count = 0; // inferred: number
let flags = [true, false]; // inferred: boolean[]
// Annotate when inference isn't enough:
let result: string | null = null;Common Mistakes
When coming from Java, developers often make these mistakes:
- TypeScript uses structural typing — if an object has the right shape, it's compatible, no explicit declaration needed
- Java uses nominal typing — a class must explicitly say 'implements Interface' to be compatible
- Java has int, long, float, double; TypeScript has a single number type for all numeric values
Key Takeaways
- TypeScript uses structural typing — shape matters, not declared names
- All numeric types collapse to number (use bigint for huge integers)
- Union types (string | null) express optional and multi-type values
- Type inference means you don't need to annotate every variable