Lesson 3 of 25
Basic Types and Inference
The primitive types, how TypeScript infers a type from a value without being told, and why annotating everything is usually the wrong instinct.
TypeScript's most-used feature isn't the annotations you write — it's the ones you don't have to, because the compiler already worked out the type from the value itself. This lesson covers the primitive types and how inference decides what to assume.
The primitives
The types map directly onto JavaScript's own primitive values:
let isPublished: boolean = true;
let title: string = "Basic Types";
let minutes: number = 9;
let nothingYet: null = null;
let notSetYet: undefined = undefined;There's no separate int or float — number covers every numeric value,
matching how JavaScript itself only has one number type. string covers
both single- and double-quoted strings and template literals.
Inference: the annotation you don't write
TypeScript reads the initial value of a variable and infers its type — you
rarely need to write : string explicitly:
let title = "Basic Types"; // inferred as string, no annotation needed
let minutes = 9; // inferred as number
title = 42;
// Type 'number' is not assignable to type 'string'.Once inferred, the type sticks — reassigning title to a number is an
error, exactly as if you'd written : string yourself. Inference isn't a
weaker version of typing; it's the same checking, just without you having to
spell out something the compiler could already see.
let widens, const doesn't
This distinction trips people up the first time they see it:
let a = 5; // inferred as number — could be reassigned to any number
const b = 5; // inferred as 5 — a literal type, since it can never changeBecause a can be reassigned, TypeScript widens the specific value 5 to
the general type number — otherwise a = 10 would be an error, which
would be useless. Because b is a const and can never change, TypeScript
keeps the most precise type it can: the literal type 5. b is not just
"a number" — it is, provably, always 5.
const b = 5;
let x: number = b; // fine — 5 is a number
let y: 10 = b;
// Type '5' is not assignable to type '10'.Literal types like this become genuinely useful once you start writing
unions ("pending" | "done" | "failed") and as const, both covered later
in this track.
Arrays and tuples, briefly
An array of a single element type is written with [] or the generic
Array<T> form — both mean exactly the same thing:
let scores: number[] = [10, 20, 30];
let names: Array<string> = ["Priya", "Jae"];A different-looking but easy to confuse syntax, [string], is a tuple — a
fixed-length array where each position has its own type. Tuples get a full
lesson of their own shortly; for now, just don't reach for square brackets
around a type expecting an array.
null, undefined, and strictNullChecks
Under strict mode (which this track assumes, and which every real project
should turn on — covered in depth later), null and undefined are their
own distinct types and are not silently part of every other type:
function getLength(text: string): number {
return text.length;
}
let maybeText: string | undefined = undefined;
getLength(maybeText);
// Argument of type 'string | undefined' is not assignable
// to parameter of type 'string'.Without strictNullChecks, that call would silently compile and crash at
runtime the moment .length runs on undefined — exactly the bug type
checking exists to prevent. Handling this properly (unions, narrowing,
optional chaining) is the subject of several lessons still to come; for now,
the important fact is that string alone means "definitely a string," never
"a string, or maybe nothing."
When to annotate anyway
If inference already works, why annotate at all? A few real cases:
- Function parameters. TypeScript cannot infer a parameter's type from
nothing —
function greet(name)leavesnameimplicitlyanywithout an annotation (or an error, undernoImplicitAny). - Widening you don't want reversed later.
let status = "pending";infersstring, not the literal"pending"— if you later need"pending" | "done", an explicit annotation up front avoids surprises. - Documenting intent for a reader, even where inference would get it right anyway — particularly on exported functions, where the signature is effectively public API.
Try it yourself
Hover-equivalent: change let to const on the second line and see how the
inferred type — and therefore what's allowed — changes.
What to remember
- TypeScript infers a type from a value's initializer — annotate only where inference can't or where being explicit matters.
letwidens a literal value to its general type;constkeeps the narrowest possible literal type, since it can never change.string[]andArray<string>are the same array type;[string]is a different thing — a tuple.- Under
strictmode,nullandundefinedare distinct types that must be explicitly included in a union to be allowed.
Check yourself
4 questions · pass 3/4 to unlock Functions in TypeScript
1.Given
let count = 5;with no annotation, what type does TypeScript infer forcount?2.What type does
const count = 5;infer, and why does it differ fromlet?3.Which of these is the correct way to type an array of strings?
4.What is the practical difference between
nullandundefinedin TypeScript's type system?
4 left to answer