TypeScript lesson 2 of 9
Basic Types: string, number, and boolean
How TypeScript's three core primitive types work, the difference between writing a type annotation and letting the compiler infer one, and what happens the moment a value stops matching its type.
Published · Every example on this page was run before it was published.
In plain JavaScript, a variable can hold a number today and a string tomorrow, and nothing warns you
either way. TypeScript's whole job is to close that gap: it lets you pin down what kind of value a
name is allowed to hold, and it checks every single place that name is used against that promise. This
lesson covers the three types you will write constantly - string, number, and boolean - and the
two ways TypeScript learns what type a value has: because you wrote it down, or because it worked it
out on its own.
The Three Primitives You Will Write Constantly
A type annotation is a colon followed by a type name, written right after the thing being typed. On a variable, it goes after the variable's name:
let city: string = "Nairobi";
let elevation: number = 1795;
let isCapital: boolean = true;
console.log(city, elevation, isCapital);Nairobi 1795 truestring covers text of any length, written in single quotes, double quotes, or backticks. number
covers every numeric value - TypeScript does not separate whole numbers from decimals the way some
languages do, so 1795 and 17.95 are both simply number. boolean holds exactly one of two values,
true or false.
let count: number = 3;
let ratio: number = 0.5;
console.log(count, ratio);3 0.5Both count and ratio are number, even though one is a whole number and the other is not - there is
only one numeric type to reach for.
Type Annotations Are Compiler-Only
The colon-and-type-name syntax is not JavaScript - it belongs entirely to TypeScript, and it exists only
for the compiler's benefit. Once your program has passed its check, the annotations have done their
job; the code that actually runs behaves exactly like the equivalent JavaScript, with no trace of the
types left in it. Writing let elevation: number does not make the number stored in elevation behave
any differently at runtime than it would in plain JavaScript - it only changes what the compiler will
let you do with elevation before the program is allowed to run at all.
Type Inference
You do not have to annotate every variable. When a variable is declared with an initial value, TypeScript looks at that value and infers the type from it automatically - you get exactly the same protection as if you had written the annotation yourself.
let temperature = 21.5;
console.log(temperature);21.5No annotation appears anywhere on that line, but temperature is typed number all the same, because
21.5 is a number. An editor hovering over temperature would show let temperature: number, even
though you never typed the word. The inferred type is enforced exactly as strictly as a written one:
let temperature = 21.5;
temperature = "warm";main.ts(2,1): error TS2322: Type 'string' is not assignable to type 'number'.The compiler never saw the word number in your source, but it still knows temperature is one,
because that is what its initial value was. Reassigning it to a string is rejected exactly the way it
would be if you had written let temperature: number = 21.5; by hand.
When You Must Annotate
If a variable is declared without an initial value, there is nothing for the compiler to infer a type from, so writing the annotation yourself is the only way to get the same checking on every later assignment:
let total: number;
total = 10 + 25;
console.log(total);35Without the : number here, TypeScript would still let the program run, but the very first assignment
to total would be the thing that quietly decided its type, rather than that decision being visible up
front on the declaration itself. Writing the annotation states your intent before a single value has
been assigned, which matters most for variables whose first assignment is buried somewhere later in the
function rather than sitting right next to the declaration.
Common Mistakes
Trying to reassign a const. This is not really about types at all, but it is one of the first
errors most people meet, and TypeScript reports it with its own compiler error rather than leaving it
to run and fail later:
const limit = 10;
limit = 20;main.ts(2,1): error TS2588: Cannot assign to 'limit' because it is a constant.const means the name can never be bound to a different value after its declaration - use let for a
variable you intend to reassign.
Assuming boolean accepts anything "truthy," the way an if condition does in plain JavaScript.
JavaScript will happily treat 1, "yes", or any non-empty object as true-ish inside a condition, but
that is a runtime behavior of if, not a rule about the boolean type. A variable annotated boolean
accepts only the two literal values true and false:
let flag: boolean = 1;main.ts(1,5): error TS2322: Type 'number' is not assignable to type 'boolean'.If you want flag to end up true or false based on a number, write the comparison yourself, such
as const flag: boolean = count > 0;.
Believing a type annotation converts a value. Writing let age: number = "30" does not parse the
string into a number for you - the annotation only checks, it never transforms. The compiler would
reject that line for the same reason it rejected temperature = "warm" above. To actually convert text
into a number, call Number(...) (or parseInt/parseFloat) and store the result.
Trusting that a number-typed value is a meaningful number. The type system guarantees the value is
some number, not that it is the number you expected. Number() on text that does not describe a
number produces NaN ("not a number"), and NaN is still, type-wise, a perfectly ordinary number -
so nothing about it trips the compiler:
let score: number = Number("abc");
console.log(score);
console.log(score === score);NaN
falseThat last line is not a typo: by definition, NaN is never equal to anything, including itself. If a
value might come from unreliable text, checking with Number.isNaN(score) after converting is worth
doing - the type system alone will not catch this for you.
A Worked Example
A small program tracking failed login attempts, mixing an explicit annotation, an inferred type, and a computed boolean:
const username: string = "arun92";
let attempts = 0;
let isLocked: boolean = false;
const maxAttempts = 3;
attempts = attempts + 1;
attempts = attempts + 1;
isLocked = attempts >= maxAttempts;
console.log("User:", username);
console.log("Attempts:", attempts);
console.log("Locked:", isLocked);User: arun92
Attempts: 2
Locked: falseusername is annotated explicitly; attempts and maxAttempts are left to inference, since their
initial values make the intended type obvious. isLocked starts as an explicit boolean and is later
recomputed from a comparison, which itself produces a boolean - attempts >= maxAttempts cannot
produce anything else, so the reassignment on line 8 passes the check without any extra annotation
needed there.
Next Steps
The valid-palindrome practice problem is a natural next step: it is built almost entirely out of
string and boolean values, and getting comfortable with how TypeScript treats each of them now will
make that problem read cleanly.
In the TypeScript playground, declare one variable of each of the three
types covered here, then try assigning a mismatched value to each one in turn and read the three
different error messages. Then declare a variable with no annotation and no initial value at all
(let x;), assign it a number, and check what an editor reports its type as - it behaves differently
from every example above, and the next lessons will give you the vocabulary to describe why.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.