Basic Types
Reviewed & published by Brayan K
By the end of this lesson you'll be able to label every value in your code with the right type — text, numbers, true/false, lists, and fixed-shape tuples — and you'll know exactly when to reach for any, unknown, or never. This is the foundation that makes every later TypeScript feature click.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Use the primitive types string, number, and boolean correctly
- Type arrays two ways — number[] and Array<T> — and know they're identical
- Build tuples: fixed-length arrays with a type for each position
- Choose between any, unknown, and never (and why any defeats the point)
- Handle null and undefined deliberately, not by accident
- Decide when to let TypeScript infer a type vs writing it explicitly, plus literal types
💡 Real-World Analogy
A type is a label on a container. A jar labelled "Sugar" (string) is meant for text; a measuring cup labelled "Cups" (number) is for numbers; a light switch (boolean) is only ever on or off. Plain JavaScript lets you pour anything into any jar and only finds out it was wrong when something breaks at runtime. TypeScript checks the labels before your program runs and stops you pouring salt into the sugar jar. An array is a row of identical jars; a tuple is a labelled tray where slot 1 is always sugar, slot 2 is always flour, in that exact order.
📊 The Core Types at a Glance
| Type | Holds | Example |
|---|---|---|
| string | Text | let n: string = "Al"; |
| number | Whole or decimal | let a: number = 9.99; |
| boolean | true / false | let ok: boolean = true; |
| number[] | List of one type | let s: number[] = [1, 2]; |
| [string, number] | Fixed tuple | ["Al", 28] |
| null / undefined | Absence of a value | let x: string | null = null; |
Note: TypeScript type names are lowercase — use string, not String. The capitalised String is the rarely-needed wrapper object, and using it is a classic beginner slip.
1. Primitive Types
A primitive is a single, simple value. The three you'll use constantly are string (text), number (any number — TypeScript has no separate int/float), and boolean (only true or false). In a .ts file you write the type after a colon: let age: number = 28;. Read this worked example, run it, then you'll write your own.
// A variable's TYPE fixes what kind of value it can hold.
// In TypeScript you can write the type after a colon: let name: string = "Alice";
// The console.logs below run as plain JavaScript so you can see the values.
// string — text, in "double" or 'single' quotes or `backticks`
let firstName = "Alice"; // type: string
let greeting = `Hello, ${firstName}!`; // backticks let you embed ${...}
console.log(greeting); // Hello, Alice!
// number — ONE type for integers AND decimals (no separate int/float)
let age = 28; // type: number
let price = 9.99; // also a number
console.log(age, price); // 28 9.99
// boolean — only ever true or false (lowercase!)
let isActive = true; // type: boolean
console.log("isActive:", isActive); // isActive: true
// In a .ts file you could write the types explicitly:
// let firstName: string = "Alice";
// let age: number = 28;
// let isActive: boolean = true;Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints in the comments, then run it.
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
// 1) A string called "city" set to a city name
let city = ___; // 👉 text in "double quotes"
// 2) A number called "year" set to the current year
let year = ___; // 👉 a whole number, e.g. 2026
// 3) A boolean called "isOpen" set to true
let isOpen = ___; // 👉 true or false (no quotes)
// These already work once your variables exist:
console.log(`In ${year}, the shop in ${city} is open: ${isOpen}`);
// ✅ Expected output (example):
// In 2026, the shop in London is open: true🔎 Deep Dive: Inference vs Explicit Annotations
You don't always have to write the type. When you give a variable a value, TypeScript infers (figures out) the type for you. Both lines below mean exactly the same thing — the second is just less typing:
let city: string = "London"; // explicit annotation
let city = "London"; // inferred — TypeScript knows it's string
// Either way, this is now an ERROR:
city = 42; // ❌ Type 'number' is not assignable to type 'string'When to be explicit: on function parameters and return types, and whenever the value doesn't make the type obvious. When to let it infer: simple let/const with an obvious literal. Idiomatic TypeScript leans on inference — don't clutter let count = 0; with a redundant : number.
🔎 Deep Dive: Literal Types
A type can be narrower than "any string" — it can be one specific value, called a literal type. Combine a few with | ("or") and you get a tidy set of allowed options, like a dropdown menu:
let direction: "up" | "down"; // only these two strings are allowed
direction = "up"; // ✅ fine
direction = "left"; // ❌ '"left"' is not assignable
// This is also why const is "tighter" than let:
const mode = "dark"; // type is the literal "dark", not just string
let theme = "dark"; // type widens to stringLiteral types let you describe rules like "status is 'active', 'paused', or 'closed'" and have the compiler reject anything else — far safer than a loose string.
2. Arrays & Tuples
An array holds many values of the same type. You write the type as number[] ("array of number") or, identically, as Array<number> using a generic — pick whichever you find clearer. A tuple is a fixed-length array where each position has its own type, like [string, number, boolean]; it's perfect for a value that always has the same shape, such as a coordinate or a row of data.
// === Arrays — many values of the SAME type ===
// In TypeScript: let scores: number[] = [95, 87, 92];
// The "number[]" means "an array where every element is a number".
let scores = [95, 87, 92];
console.log("scores:", scores); // scores: [ 95, 87, 92 ]
console.log("first:", scores[0]); // first: 95
console.log("how many:", scores.length); // how many: 3
let names = ["Alice", "Bob"]; // string[]
names.push("Charlie"); // adding another string is fine
console.log("names:", names); // names: [ 'Alice', 'Bob', 'Charlie' ]
// Array<T> is the SAME thing, written with a generic.
// let scores: Array<number> = [95, 87, 92]; // identical to number[]
// === Tuples — a FIXED-length array, one type per position ===
// In TypeScript: let user: [string, number, boolean] = ["Alice", 28, true];
// Position 0 must be string, 1 must be number, 2 must be boolean.
let user = ["Alice", 28, true];
console.log("name:", user[0]); // name: Alice (string slot)
console.log("age: ", user[1]); // age: 28 (number slot)
console.log("active:", user[2]); // active: true (boolean slot)
// A tuple is great for a value that always has the same shape,
// like an (x, y) coordinate:
let point = [10, 20]; // [number, number]
console.log("x =", point[0], "y =", point[1]); // x = 10 y = 20Now you try. Build a couple of arrays and a tuple, then run the program to check the output.
// 🎯 YOUR TURN — fill in the blanks marked with ___
// 1) An array of three numbers (the type would be number[])
let temps = ___; // 👉 e.g. [18, 21, 19]
// 2) Add one more reading to the end of the array
temps.push(___); // 👉 any number, e.g. 22
// 3) A tuple describing a product: [name (string), price (number)]
let product = ___; // 👉 e.g. ["Coffee", 2.5]
console.log("readings:", temps);
console.log(`${product[0]} costs £${product[1]}`);
// ✅ Expected output (example):
// readings: [ 18, 21, 19, 22 ]
// Coffee costs £2.53. any vs unknown vs never
These three special types are about the edges of the type system. any switches checking off — the value can be anything and TypeScript stops protecting you (use it as a last resort). unknown is the safe sibling: it can hold anything too, but TypeScript forces you to narrow (prove) the type before you use it. never is the type with no possible values — the return type of a function that always throws or loops forever.
// === any — turns OFF type checking (use as a last resort) ===
// "any" tells TypeScript "trust me, don't check this".
// It compiles, but you lose every safety net:
let data = 42;
data = "hello"; // with type 'any' this is allowed
data = [1, 2, 3]; // ...and so is this
console.log("data is now:", data); // data is now: [ 1, 2, 3 ]
// The danger: data.foo.bar would COMPILE but 💥 crash at runtime.
// === unknown — the SAFE alternative to any ===
// You CAN store anything in an 'unknown', but you must CHECK its
// type before you use it. TypeScript forces you to be careful.
function describe(value) {
// "Narrowing": prove what the type is, then use it safely.
if (typeof value === "string") {
return "string of length " + value.length;
} else if (typeof value === "number") {
return "number doubled = " + value * 2;
}
return "some other type";
}
console.log(describe("hi")); // string of length 2
console.log(describe(21)); // number doubled = 42
// === never — a value that can NEVER happen ===
// A function that always throws (or loops forever) returns 'never'
// because it never produces a normal value.
// function fail(msg: string): never { throw new Error(msg); }
console.log("'never' = the type with no possible values");
// In a .ts file these annotations would look like:
// let data: any = 42;
// let input: unknown = getInput();
// function fail(msg: string): never { throw new Error(msg); }🔎 Deep Dive: null and undefined
Both represent "no value", but with different intent. undefined means "this was never set". null means "intentionally empty — there's deliberately nothing here". You allow a value to be missing by adding it to the type with |:
let nickname: string | null = null; // explicitly "no nickname yet"
nickname = "Ace"; // later, give it a value
let middleName: string | undefined; // not set yet -> undefined
// With "strictNullChecks" on (the recommended default), TypeScript
// makes you handle the empty case before using the value:
if (nickname !== null) {
console.log(nickname.toUpperCase()); // ✅ safe — we checked first
}This is one of TypeScript's biggest wins: it turns "cannot read property of null" runtime crashes into compile-time errors you fix before shipping.
📝 The Type Syntax (read-only reference)
This is what the same ideas look like with full TypeScript annotations. You can't run it here (it's TS-only syntax), but it's worth reading closely:
// Primitives
let firstName: string = "Alice";
let age: number = 28;
let isActive: boolean = true;
// Arrays — two identical ways to write the type
let scores: number[] = [95, 87, 92];
let scores2: Array<number> = [95, 87, 92];
// Tuple — one type per position, fixed length
let user: [string, number, boolean] = ["Alice", 28, true];
// Absence of a value
let nickname: string | null = null;
let middleName: string | undefined;
// Literal types — a fixed set of allowed values
let status: "active" | "paused" | "closed" = "active";
// The special types
let anything: any = 42; // checking OFF (avoid)
let safe: unknown = getInput(); // must narrow before use
function fail(msg: string): never { throw new Error(msg); }Pro Tips
- 💡 Reach for unknown, never any, when a value's type is genuinely uncertain. unknown keeps the safety net; any removes it everywhere it spreads.
- 💡 Let TypeScript infer simple values: let count = 0; is cleaner than let count: number = 0;. Be explicit on function signatures.
- 💡 Use lowercase type names: string, number, boolean — not String, Number, Boolean.
- 💡 Use a tuple, not an array, when the length and the meaning of each slot are fixed (e.g. [lat, lng]).
Common Errors (and the fix)
- "TS2322: Type 'number' is not assignable to type 'string'" — you put a value of the wrong type into a typed variable, e.g. let name: string = 42;. Fix the value ("42") or fix the type (number).
- "TS2345: Argument of type 'string' is not assignable to parameter of type 'number'" — you passed the wrong type into a function call, like double("5") where double(n: number) was expected. Convert it first: double(Number("5")).
- Why any defeats the point: let x: any = ...; makes x.anything.goes compile happily and then crash at runtime. any silences the very checker you installed TypeScript for — use unknown and narrow instead.
- "TS2532: Object is possibly 'undefined'" — you used a value that might be empty. Check it first: if (x !== undefined) { ... }, or use x?.prop.
- Capital-letter types: let s: String = "hi"; compiles but is wrong style. Use the lowercase primitive string.
📋 Quick Reference — Types
| Type | Means | Example |
|---|---|---|
| string | Text | let s: string = "hi" |
| number | Any number | let n: number = 42 |
| boolean | true / false | let b: boolean = true |
| T[] / Array<T> | List of one type | let a: number[] = [1, 2] |
| [A, B] | Fixed tuple | ["Al", 28] |
| "a" | "b" | Literal union | let m: "on" | "off" |
| null / undefined | No value | string | null |
| any | Checking off (avoid) | let x: any = 1 |
| unknown | Safe "anything" | let x: unknown = f() |
| never | No possible value | function fail(): never |
Frequently Asked Questions
Q: Is there really no separate type for integers and decimals?
Correct — like JavaScript, TypeScript has one number type for both 28 and 9.99. (There's also bigint for very large whole numbers, but you'll rarely need it.)
Q: What's the real difference between any and unknown?
Both can hold any value. With any you can then do anything with it and TypeScript won't complain (unsafe). With unknown you must first check the type (typeof, Array.isArray, etc.) before using it, so mistakes are caught at compile time.
Q: When should I write the type and when should I let TypeScript guess?
Let it infer simple initialised variables (let name = "Al"). Be explicit on function parameters, function return types, and any case where the value alone doesn't make the intended type obvious.
Q: Array or tuple — how do I choose?
Use an array when you have any number of items of the same type (number[]). Use a tuple when the length is fixed and each slot means something specific ([name, age]).
Mini-Challenge: Ticket Summary
No blanks this time — just a brief and a starting outline. Build it, run it, and check your output against the example in the comments. This is the kind of small, typed program real apps are made of.
// 🎯 MINI-CHALLENGE: Movie ticket summary
// In a real .ts file you'd annotate the types; here just build the values.
//
// 1. A string "title" (the film name) and a number "tickets".
// 2. A number "pricePerTicket" (e.g. 12.5).
// 3. A boolean "isMember" (members get 10% off — your choice true/false).
// 4. Work out total = tickets * pricePerTicket, then apply the
// member discount if isMember is true.
// 5. Print: "<tickets> tickets for <title>: £<total>"
//
// ✅ Example (3 tickets, £12.5 each, member=true):
// 3 tickets for Dune: £33.75
// your code here🎉 Lesson Complete
- ✅ Primitives: string (text), number (any number), boolean (true/false)
- ✅ Arrays are T[] or Array<T>; tuples are fixed-length with a type per slot
- ✅ any turns checking off — prefer unknown and narrow before use
- ✅ never is the type with no values; null/undefined are handled with | unions
- ✅ Let TypeScript infer obvious types; be explicit on function signatures
- ✅ Literal types ("on" | "off") restrict a value to a fixed set of options
Practice quiz
Which type does TypeScript use for both 28 and 9.99?
- int
- float
- number
- decimal
Answer: number. TypeScript (like JavaScript) has one number type for integers and decimals alike; there is no separate int or float.
How do you write the type for an array where every element is a number?
- number[]
- [number]
- Array(number)
- numbers
Answer: number[]. number[] (equivalently Array<number>) means an array whose every element is a number. [number] would be a one-element tuple.
What does the tuple type [string, number, boolean] describe?
- An array of any of those three types in any order
- Exactly three elements: a string, then a number, then a boolean
- An object with three keys
- A union of string, number, and boolean
Answer: Exactly three elements: a string, then a number, then a boolean. A tuple is a fixed-length array with a type per position: index 0 must be a string, 1 a number, 2 a boolean.
What does typing a value as any do?
- Restricts it to primitive values
- Turns OFF type checking for that value
- Forces you to narrow before using it
- Makes it read-only
Answer: Turns OFF type checking for that value. any switches off type checking entirely, so even nonsense like data.foo.bar compiles and may crash at runtime.
What is the safe alternative to any that forces you to check the type before use?
- never
- void
- unknown
- object
Answer: unknown. unknown accepts any value but won't let you use it until you narrow it with a check like typeof, keeping the safety net.
What is the never type?
- A value that is always null
- The type with no possible values
- Another name for any
- A type for empty strings
Answer: The type with no possible values. never is the type with no possible values - for example the return type of a function that always throws or loops forever.
Which is the correct, idiomatic primitive type name in TypeScript?
- String
- Str
- string
- Text
Answer: string. Type names are lowercase: string, number, boolean. Capitalised String is the wrapper object and is a classic beginner slip.
Given let mode = "dark" vs const mode = "dark", how do their inferred types differ?
- Both are the literal type "dark"
- let is the literal "dark"; const widens to string
- let widens to string; const is the literal "dark"
- Both widen to string
Answer: Both widen to string. A const holds the narrow literal type "dark", while a let widens to the broader type string because it can be reassigned.
How do you allow a variable to be a string OR explicitly null?
- string null
- string | null
- string & null
- string?
Answer: string | null. A union with | lets a value be one of several types: string | null permits a string or the value null.
When is it most idiomatic to write an explicit type annotation?
- On every let and const, always
- Never - always rely on inference
- On function parameters and return types
- Only on boolean variables
Answer: On function parameters and return types. Let inference handle obvious initialised variables; be explicit on function parameters and return types where intent isn't obvious.
Continue this course
- Previous: Introduction to TypeScript
- Next: Interfaces and Type Aliases — Define the shape of objects and create reusable custom types
- Quick reference: TypeScript cheat sheet › Basic Types