Lesson 2 • Beginner
Basic Types 🧱
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.
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
console.log(...). How the examples run: TypeScript compiles to JavaScript, so the runnable boxes execute JavaScript to show you the values. The TypeScript-only type annotations appear in read-only blocks and code comments. Install TypeScript to get full compile-time checking on your own machine.💡 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.
Worked example: string, number, boolean
Read every comment, run it, and check the output matches.
// 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 integer
...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: declare three values
Fill in the ___ blanks, then check your output against the expected lines.
// 🎯 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}`);
// ✅ E
...🔎 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 string
Literal 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.
Worked example: arrays & tuples
See typed lists and fixed-shape tuples in action.
// === 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
...Now you try. Build a couple of arrays and a tuple, then run the program to check the output.
🎯 Your turn: arrays and a tuple
Fill in the ___ blanks, then check against the expected 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 (e
...3. 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.
Worked example: any, unknown, never
See why any is dangerous and how unknown forces a type check.
// === 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
...🔎 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, neverany, when a value's type is genuinely uncertain.unknownkeeps the safety net;anyremoves it everywhere it spreads. - 💡 Let TypeScript infer simple values:
let count = 0;is cleaner thanlet count: number = 0;. Be explicit on function signatures. - 💡 Use lowercase type names:
string,number,boolean— notString,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")wheredouble(n: number)was expected. Convert it first:double(Number("5")). - Why
anydefeats the point:let x: any = ...;makesx.anything.goescompile happily and then crash at runtime.anysilences the very checker you installed TypeScript for — useunknownand narrow instead. - "TS2532: Object is possibly 'undefined'" — you used a value that might be empty. Check it first:
if (x !== undefined) { ... }, or usex?.prop. - Capital-letter types:
let s: String = "hi";compiles but is wrong style. Use the lowercase primitivestring.
📋 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: build a ticket summary
Declare the values yourself and print the formatted total.
// 🎯 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.
...🎉 Lesson Complete
- ✅ Primitives:
string(text),number(any number),boolean(true/false) - ✅ Arrays are
T[]orArray<T>; tuples are fixed-length with a type per slot - ✅
anyturns checking off — preferunknownand narrow before use - ✅
neveris the type with no values;null/undefinedare 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 - ✅ Next lesson: Interfaces & Type Aliases — describe the shape of whole objects
Sign up for free to track which lessons you've completed and get learning reminders.