TypeScript · Topic 1 of 8
Basic types and type inference
What you will learn
TypeScript adds a static type to every value so mistakes are caught while you write code, not when users run it. You can state types yourself with annotations, but the compiler also works many of them out for you, which is called type inference. Knowing the basic types and how inference behaves is the foundation for everything else in the language.
The primitive types
The everyday primitives are string, number, boolean, bigint, null and undefined. There is one number type for integers and decimals, and a separate bigint type for values like 10n, so the two are not interchangeable. Always write the lowercase names: String, Number and Boolean (capitalised) are wrapper object types and should not be used for annotations.
TypeScript never converts values for you. Assigning "25" to a number variable is a compile-time error, and so is arithmetic on a string.
Example
The primitive types
let name: string = "Ada";
let age: number = 36;
let done: boolean = false;
let big: bigint = 10n;
// age = "25"; // Error: string is not assignable to number
// let s: String = "a"; let t: string = s; // Error: wrapper type String is not assignable to stringArrays, tuples and unions
Write an array of numbers as number[] (or Array<number>). A tuple such as [string, number] is different: it has exactly two elements in a fixed order, and reading an index that does not exist is an error. A union like string | number says a value may be one of several types.
Example
Arrays, tuples and unions
let scores: number[] = [90, 85];
let pair: [string, number] = ["a", 1];
let id: string | number = 7;
id = "abc"; // fine, still in the union
// console.log(pair[2]); // Error: tuple has no element at index 2
// scores.push("4"); // Error: string is not assignable to numberType inference
When you give a variable an initial value, TypeScript infers its type, so you usually do not need an annotation. A let variable gets the general type (let x = 10 is number), while a const gets the exact literal type (const c = "hi" is "hi") because it can never change. Object properties are mutable, so { k: "v" } has k of type string.
Arrays of mixed values infer a union element type, and a conditional expression infers a union of both branches. A function's return type is inferred from its return statements.
Example
Type inference
let x = 10; // number
const c = "hi"; // "hi"
const mixed = [1, "a", true]; // (string | number | boolean)[]
const o = { k: "v" }; // { k: string }
function add(a: number, b: number) { return a + b; } // returns number
// x = "ten"; // Error: inferred as number
// console.log(o.missing); // Error: property does not existWidening and literal types
A let variable initialised with a string widens to string, even if the text is "left". Passing it to a function that only accepts "left" | "right" then fails. Declaring it with const, or annotating the literal union, keeps the narrow type.
Also remember that const stops reassignment, not mutation: you can still change a property of a const object.
Example
Widening and literal types
function move(d: "left" | "right") {}
const dir = "left";
move(dir); // ok: dir is "left"
let dir2 = "left";
// move(dir2); // Error: dir2 widened to string
const user = { id: 1 };
user.id = 2; // allowedany, unknown, void and null
any switches off type checking for that value, and anything copied from an any value is also any. Prefer unknown: it accepts any value but forces you to narrow it (for example with typeof) before use. void marks a function that returns nothing useful.
With strictNullChecks (part of strict mode), null and undefined are their own types, so a value typed string | null must be checked before you use it.
Example
any, unknown, void and null
let v: unknown = "hello";
if (typeof v === "string") {
console.log(v.toUpperCase()); // ok after narrowing
}
function len(s: string | null) {
return s ? s.length : 0; // null handled
}
// return s.length; // Error: s is possibly nullKey points
- Use lowercase primitives: string, number, boolean, bigint.
- Inference gives let a general type and const a literal type.
- number[] is an array of any length; [string, number] is a fixed tuple.
- A union such as string | number allows one of several types.
- Prefer unknown over any, and narrow before using it.
- With strictNullChecks, null and undefined must be handled explicitly.
Common mistakes
- Annotating with String or Number instead of string or number.
- Expecting TypeScript to convert types automatically, such as "5" * 2.
- Using any to silence an error, which hides real bugs.
- Forgetting that a let string widens and no longer matches a literal union.
Lesson complete
Ready to check what you learned?
Swipe left or right, or use the buttons.