Skip to content

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:

TypeScript
let city: string = "Nairobi";
let elevation: number = 1795;
let isCapital: boolean = true;

console.log(city, elevation, isCapital);
Output
Nairobi 1795 true

string 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.

TypeScript
let count: number = 3;
let ratio: number = 0.5;
console.log(count, ratio);
Output
3 0.5

Both 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.

TypeScript
let temperature = 21.5;
console.log(temperature);
Output
21.5

No 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:

TypeScript
let temperature = 21.5;
temperature = "warm";
Output
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:

TypeScript
let total: number;
total = 10 + 25;
console.log(total);
Output
35

Without 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:

TypeScript
const limit = 10;
limit = 20;
Output
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:

TypeScript
let flag: boolean = 1;
Output
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:

TypeScript
let score: number = Number("abc");
console.log(score);
console.log(score === score);
Output
NaN
false

That 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:

TypeScript
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);
Output
User: arun92
Attempts: 2
Locked: false

username 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.