Episode 1 — Fundamentals / 1.25 — TypeScript Essentials
1.25.d — Basic Types
In one sentence: TypeScript provides a rich set of built-in types —
string,number,boolean,any,unknown,void,never, arrays, tuples, enums, and literal types — that let you describe the shape and constraints of your data with precision.
Navigation: ← 1.25 Overview · 1.25.e — Interfaces vs Type Aliases →
1. Type annotation syntax
TypeScript uses a colon after a variable name, parameter, or return position to annotate a type:
// Variable annotation
let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
// Function parameter and return type annotations
function add(a: number, b: number): number {
return a + b;
}
// Arrow function
const multiply = (a: number, b: number): number => a * b;
2. string — text values
let firstName: string = "Alice";
let greeting: string = `Hello, ${firstName}!`; // Template literals work
let empty: string = "";
// All string methods work with full autocomplete:
firstName.toUpperCase(); // "ALICE"
firstName.includes("li"); // true
firstName.length; // 5
3. number — all numeric values
TypeScript has one number type for integers, floats, hex, binary, and octal — no separate int or float:
let integer: number = 42;
let float: number = 3.14;
let negative: number = -10;
let hex: number = 0xff; // 255
let binary: number = 0b1010; // 10
let octal: number = 0o744; // 484
let big: number = 1_000_000; // Underscore separators allowed
// NaN and Infinity are also 'number'
let notANumber: number = NaN;
let infinite: number = Infinity;
For very large integers, use bigint:
let huge: bigint = 9007199254740991n;
4. boolean — true/false
let isLoggedIn: boolean = true;
let hasPermission: boolean = false;
// Boolean expressions produce boolean values
let isAdult: boolean = age >= 18; // true
5. any — opt out of type checking
any tells TypeScript to skip all checking for this value. It accepts any type and can be used as any type:
let data: any = "hello";
data = 42; // No error
data = { x: 1 }; // No error
data.foo.bar.baz(); // No error — even if this crashes at runtime!
// 'any' is contagious — it infects everything it touches
let result: any = getData();
let name = result.name; // 'name' is also 'any' — no type safety
Avoid any whenever possible. It defeats the purpose of TypeScript. Use unknown instead (see next section).
6. unknown — type-safe alternative to any
unknown is the safe counterpart to any. You can assign anything to unknown, but you must narrow the type before using it:
let data: unknown = fetchSomething();
// Cannot use directly — errors:
// data.toUpperCase(); // Error: 'data' is of type 'unknown'
// data + 1; // Error: 'data' is of type 'unknown'
// Must narrow first:
if (typeof data === "string") {
data.toUpperCase(); // OK — TypeScript knows it's a string here
}
if (typeof data === "number") {
data + 1; // OK — TypeScript knows it's a number here
}
any vs unknown:
any | unknown | |
|---|---|---|
| Assign anything to it | Yes | Yes |
| Use it without checking | Yes (unsafe!) | No — must narrow first |
| Autocomplete | None | Full, after narrowing |
| Recommended | Migration only | Yes, for truly unknown data |
7. void — function returns nothing
void indicates a function does not return a meaningful value:
function logMessage(message: string): void {
console.log(message);
// No return statement, or: return; (no value)
}
// void is NOT the same as undefined
// void means "I don't care about the return value"
8. null and undefined as types
By default (with strictNullChecks), null and undefined are their own types and cannot be assigned to other types:
let maybeString: string | null = null; // Explicitly nullable
let maybeName: string | undefined = undefined; // Explicitly optional
// Without union, these are errors:
// let name: string = null; // Error with strictNullChecks
// let age: number = undefined; // Error with strictNullChecks
9. never — function never returns
never represents values that never occur. Used for functions that always throw or have infinite loops:
// Function that always throws
function throwError(message: string): never {
throw new Error(message);
}
// Function with infinite loop
function infiniteLoop(): never {
while (true) {
// ...
}
}
// 'never' is also the type of impossible cases in exhaustive checks:
type Shape = "circle" | "square";
function getArea(shape: Shape): number {
switch (shape) {
case "circle": return Math.PI * 10 * 10;
case "square": return 10 * 10;
default:
const _exhaustive: never = shape; // Error if a case is missed
return _exhaustive;
}
}
10. Type inference
TypeScript can automatically infer types from assigned values — you do not always need explicit annotations:
// TypeScript infers these types automatically:
let name = "Alice"; // inferred as: string
let age = 30; // inferred as: number
let isActive = true; // inferred as: boolean
let items = [1, 2, 3]; // inferred as: number[]
let user = { name: "Alice", age: 30 }; // inferred as: { name: string; age: number }
// Function return types are also inferred:
function add(a: number, b: number) { // return type inferred as: number
return a + b;
}
When to annotate vs let inference work
| Situation | Recommendation |
|---|---|
| Variable initialized with a value | Let inference work — let x = 5 |
| Function parameters | Always annotate — function foo(x: number) |
| Function return types | Annotate for public APIs, let inference work for internal helpers |
| Complex objects | Annotate with interface/type for clarity |
| Empty arrays/objects | Annotate — inference cannot guess: let items: string[] = [] |
| Uninitialized variables | Annotate — let name: string; |
// Good — inference handles it
const message = "Hello"; // string
const numbers = [1, 2, 3]; // number[]
// Good — must annotate (empty or uninitialized)
let results: string[] = []; // Cannot infer from empty array
let count: number; // No initializer
// Good — annotate parameters, return type for clarity
function greet(name: string): string {
return `Hello, ${name}!`;
}
11. Arrays
Two equivalent syntaxes for typed arrays:
// Syntax 1: type[]
let names: string[] = ["Alice", "Bob", "Charlie"];
let scores: number[] = [95, 87, 92];
// Syntax 2: Array<type> (generic syntax)
let names: Array<string> = ["Alice", "Bob", "Charlie"];
let scores: Array<number> = [95, 87, 92];
// Mixed arrays with union types
let mixed: (string | number)[] = ["Alice", 30, "Bob", 25];
// Array methods respect the type:
names.push("Dave"); // OK
names.push(42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'
// Readonly arrays
const frozenList: readonly string[] = ["a", "b", "c"];
// frozenList.push("d"); // Error: Property 'push' does not exist on type 'readonly string[]'
12. Tuples — fixed-length typed arrays
Tuples are arrays with a fixed number of elements where each position has a specific type:
// A tuple of [string, number]
let person: [string, number] = ["Alice", 30];
// Each position is typed:
person[0].toUpperCase(); // OK — position 0 is string
person[1].toFixed(2); // OK — position 1 is number
// person[0].toFixed(2); // Error — string has no toFixed
// Common use: function returning multiple values
function getCoordinates(): [number, number] {
return [40.7128, -74.0060];
}
const [lat, lng] = getCoordinates(); // Destructuring works
// Named tuples (for documentation)
type UserTuple = [name: string, age: number, isActive: boolean];
const user: UserTuple = ["Alice", 30, true];
// Optional tuple elements
type Range = [start: number, end?: number];
const range1: Range = [0, 10];
const range2: Range = [0]; // 'end' is optional
// Rest elements in tuples
type StringAndNumbers = [string, ...number[]];
const data: StringAndNumbers = ["scores", 95, 87, 92, 100];
13. Enums — named constants
Enums define a set of named constants:
// Numeric enum (default — values auto-increment from 0)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
let move: Direction = Direction.Up;
console.log(move); // 0
console.log(Direction[0]); // "Up" (reverse mapping)
// String enum (recommended — more readable in logs/debugging)
enum Status {
Pending = "PENDING",
Active = "ACTIVE",
Inactive = "INACTIVE",
}
let userStatus: Status = Status.Active;
console.log(userStatus); // "ACTIVE"
// Using enums in conditions
if (userStatus === Status.Active) {
console.log("User is active");
}
// Enum as function parameter
function handleStatus(status: Status): string {
switch (status) {
case Status.Pending: return "Waiting...";
case Status.Active: return "Good to go!";
case Status.Inactive: return "Account disabled.";
}
}
// const enum — inlined at compile time (smaller output)
const enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
Note: Many TypeScript developers prefer union literal types over enums for simplicity:
// Union literal — often preferred over enum
type Direction = "up" | "down" | "left" | "right";
type Status = "pending" | "active" | "inactive";
14. Literal types — exact values as types
You can use specific values as types, not just broad categories:
// String literals
let direction: "up" | "down" | "left" | "right";
direction = "up"; // OK
direction = "diagonal"; // Error: Type '"diagonal"' is not assignable
// Number literals
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 3; // OK
let bad: DiceRoll = 7; // Error
// Boolean literal
type True = true;
// Combining literals for precise types
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type StatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 500;
function request(method: HttpMethod, url: string): Promise<{ status: StatusCode }> {
// ...
}
// 'as const' makes all values literal types
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
} as const;
// typeof config = { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000; readonly retries: 3 }
15. Type assertions — telling TypeScript what you know
Type assertions tell TypeScript "I know more about this type than you do":
// Syntax 1: 'as' keyword (preferred)
let value: unknown = "Hello, world!";
let length: number = (value as string).length;
// Syntax 2: angle bracket (does NOT work in .tsx files)
let length2: number = (<string>value).length;
// Common use case: DOM elements
const input = document.getElementById("username") as HTMLInputElement;
input.value = "Alice"; // Without assertion, TS only knows it's HTMLElement | null
// Non-null assertion operator (!)
const el = document.getElementById("app")!; // Assert it's not null
// Use with caution — if it IS null, you'll get a runtime error
// 'as const' assertion — makes values readonly and literal
const colors = ["red", "green", "blue"] as const;
// Type: readonly ["red", "green", "blue"] (not string[])
// 'satisfies' keyword (TypeScript 4.9+) — checks type WITHOUT widening
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// palette.red is still number[] (not string | number[])
// palette.green is still string (not string | number[])
Warning: Type assertions override the compiler — use them sparingly. If you assert wrong, you get runtime bugs that TypeScript cannot catch.
16. Summary table — all basic types at a glance
| Type | Example | Description |
|---|---|---|
string | "hello" | Text values |
number | 42, 3.14 | All numeric values (int and float) |
boolean | true, false | Logical values |
any | let x: any | Opt out of type checking (avoid) |
unknown | let x: unknown | Safe alternative to any (must narrow) |
void | (): void | Function returns nothing |
null | null | Intentional absence of value |
undefined | undefined | Variable declared but not assigned |
never | (): never | Function never returns (throws/loops) |
string[] | ["a", "b"] | Array of strings |
[string, number] | ["Alice", 30] | Tuple — fixed-length typed array |
enum Direction {} | Direction.Up | Named constants |
"up" | "down" | "up" | Literal types — exact values |
value as Type | x as string | Type assertion |
Key takeaways
- TypeScript has primitive types:
string,number,boolean, plus special typesany,unknown,void,null,undefined,never. - Type inference is powerful — you do not need to annotate everything; annotate parameters and ambiguous cases.
unknownis safer thanany— it forces you to narrow before using.- Arrays use
type[]orArray<type>; tuples use[type1, type2]for fixed-length. - Enums define named constants; literal types (
"up" | "down") are a lighter alternative. - Type assertions (
as Type) override the compiler — use sparingly and responsibly.
Explain-It Challenge
Explain without notes:
- What is the difference between
anyandunknown? - When does TypeScript require you to write a type annotation (cannot infer)?
- What is a tuple, and how does it differ from a regular array?
Navigation: ← 1.25 Overview · 1.25.e — Interfaces vs Type Aliases →