The annotation syntax
An annotation exists because a variable's type is a promise to every line that reads it later. In a real file, city might be used fifty lines below its declaration. If anything in between reassigns it to a number, every one of those reads becomes a latent crash. Writing the type down turns the promise into something the compiler enforces.
A type annotation goes after the variable name, separated by a colon:
let city: string = "Tokyo"; let population: number = 37000000; let isCapital: boolean = true;
Compare with the JavaScript you wrote before:
let city = "Tokyo"; // could be reassigned to anything
In TypeScript, city is now locked to strings. Try city = 42 later in the file and the compiler answers: Type 'number' is not assignable to type 'string'. The three primitive types you will use constantly are string, number, and boolean. Note they are lowercase, matching what JavaScript's typeof operator returns.
Locking a variable to one type
let city: string = "Tokyo"; let population: number = 37000000; console.log(city + " has about " + population + " people");
Output
Tokyo has about 37000000 peopleReassigning city to another string is perfectly fine, because the annotation constrains the type, not the value. Writing city = 5 is the error, and the compiler names it precisely: Type 'number' is not assignable to type 'string'.
That distinction matters in real code. A variable that changes value fifty times is normal. A variable that changes kind halfway down a file is almost always a bug waiting for a reader who assumed otherwise.
Inference: TypeScript often knows already
Write let city = "Tokyo" with no annotation and hover it in an editor: TypeScript reports the type is string anyway. This is type inference, the compiler deducing a type from the value you assigned. The rules real codebases follow:
- If the variable is initialized on the same line, let inference work, do not repeat yourself
- Annotate when there is no initializer, or when the inferred type is not what you intend
So let count = 0 needs no annotation, but let count: number (declared now, assigned later) does. Inference is why idiomatic TypeScript often looks nearly identical to JavaScript.
const infers more precisely than let
Inference also reacts to how the variable is declared. let city = "Tokyo" is inferred as string, because a let may be reassigned to any other string later.
const city = "Tokyo" is inferred as the exact type "Tokyo", a type whose only allowed value is that one string. A const can never change, so the compiler keeps every bit of information it has.
let a = "Tokyo"; // type: string const b = "Tokyo"; // type: "Tokyo"
You will rarely notice the difference day to day, but it explains editor tooltips, and lesson 2-3 turns these exact-value types (literal types) into a genuinely useful tool.
What inference produces for a plain number
Take the declaration let score = 10. The inferred type is number, deduced from the initial value.
Inference looks at the initializer and asks what category that value belongs to. The literal 10 is a number, so score becomes a number, and reassigning it to a string later is a compile error even though nobody wrote an annotation.
The any type, which switches checking off entirely, only appears when TypeScript has no information at all. The classic source is an unannotated function parameter, since the compiler cannot see what callers will pass. A variable with an initializer is never in that situation.
Three annotated variables, one sentence
Here are three explicit annotations describing a movie, followed by a line that prints them together.
let movie: string = "Arrival"; let year: number = 2016; let rewatchable: boolean = true; console.log(movie + " (" + year + ") rewatchable: " + rewatchable);
Output
Arrival (2016) rewatchable: true
Reading the code
- The pattern is
let name: type = value, repeated once per variable. There is no way to annotate several declarations at once. - The output string is assembled by concatenation, so the literal pieces
" (",") rewatchable: "supply the punctuation between the values. yearandrewatchableare not strings, yet concatenating them works. JavaScript converts each to text when it meets a+with a string on the other side, and TypeScript allows it. That convenience is also why annotating numbers is worth the effort: without the annotation, ayearthat arrives as"2016"from a form would concatenate just as happily and never complain.