Basic Types & Inference
Every value in a JavaScript program has a type: "Kochi" is a string, 42 is a number, true is a boolean. TypeScript lets you write these types down, and more importantly, it works most of them out for you.
In this lesson you will learn the basic (primitive) types, how to write a type annotation, and how type inference decides types when you do not write them. Knowing when to annotate and when to let TypeScript infer is one of the most useful habits you can build.
Type annotations
A type annotation is a colon followed by a type, written after a variable name or parameter:
let studentName: string = "Ananya";
let rollNumber: number = 27;
let hasPaidFee: boolean = true;
console.log(studentName, rollNumber, hasPaidFee);
console.log(typeof studentName, typeof rollNumber, typeof hasPaidFee);Ananya 27 true string number boolean
The annotation says "this variable may only hold this kind of value". Once rollNumber is a number, you cannot put text into it later. Notice that the JavaScript typeof operator agrees with the TypeScript types for these simple values, because TypeScript's primitive types are named after JavaScript's run-time types.
The primitive types
| Type | Example values | Notes |
|---|---|---|
string | "Pune", 'हिंदी', `Hi ${name}` | Any text |
number | 42, 3.14, -7, 1_299 | Whole numbers and decimals share one type |
boolean | true, false | Only these two values |
bigint | 9007199254740993n | Whole numbers of any size, written with n |
symbol | Symbol("id") | Unique keys, rarely needed |
null | null | "Deliberately empty" |
undefined | undefined | "Not set yet" |
There is no separate int or float type: number covers both, exactly like JavaScript. Use bigint only when whole numbers go beyond about 9 quadrillion (Number.MAX_SAFE_INTEGER), for example in some ID systems or cryptography.
const safeLimit: number = Number.MAX_SAFE_INTEGER;
const tooBig: number = safeLimit + 2;
const exact: bigint = BigInt(safeLimit) + 2n;
console.log(safeLimit);
console.log(tooBig);
console.log(exact);9007199254740991 9007199254740992 9007199254740993n
The number result is wrong by one because it is past the safe limit. The bigint result is exact. You cannot mix the two in one calculation without converting, which TypeScript also checks for you.
What type errors look like
When a value does not match its type, tsc reports it. Here are three common mistakes:
let city: string = "Lucknow";
city = 226001;
let price: number = 499;
price = "499";
let greeting = "namaste";
console.log(greeting.toUppercase());app.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. app.ts(5,1): error TS2322: Type 'string' is not assignable to type 'number'. app.ts(8,22): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
Read error messages slowly. "Type X is not assignable to type Y" means "you tried to put an X where a Y is required". The third error even suggests the correct method name. The variable greeting has no annotation at all, yet TypeScript still knew it was a string. That is inference.
Type inference
Type inference means TypeScript works out a type from the value you give. When you write let marks = 88, TypeScript infers number, exactly as if you had written let marks: number = 88. In VS Code, hover over a variable to see the inferred type.
let marks = 88; // inferred: number
let subject = "Physics"; // inferred: string
let passed = marks >= 33; // inferred: boolean
const fees = [1200, 1500, 900]; // inferred: number[]
const total = fees.reduce((sum, fee) => sum + fee, 0); // inferred: number
console.log(subject, marks, passed);
console.log("Total fees:", total);Physics 88 true Total fees: 3600
Inference works through expressions too: TypeScript knows that marks >= 33 gives a boolean and that adding numbers gives a number. So when should you write types yourself?
- Let TypeScript infer local variables that get a value immediately. Extra annotations add noise and can go out of date.
- Annotate function parameters. TypeScript cannot guess what callers will pass. You will see this in Functions.
- Annotate variables declared without a value, such as
let total: number;, because there is nothing to infer from. - Annotate when you want a wider or stricter type than the value suggests, for example a variable that starts as
nullbut will later hold a string.
let versus const
let and const are inferred slightly differently. let city = "Kochi" is inferred as string, because you might change it to another city. const city = "Kochi" can never change, so TypeScript infers the exact type "Kochi", called a literal type. You will use this in Literal Types, as const & satisfies.
null and undefined
With strict mode (on by default in TypeScript 7), null and undefined are separate types. A string variable cannot hold null, and TypeScript makes sure you never read a variable before giving it a value:
let nickname: string = null;
let discount: number;
console.log(discount * 2);app.ts(1,5): error TS2322: Type 'null' is not assignable to type 'string'. app.ts(4,13): error TS2454: Variable 'discount' is used before being assigned.
This check, called strict null checking, removes a whole family of bugs: "Cannot read properties of null" is one of the most common JavaScript crashes. When a value really may be missing, say so with a union type: let nickname: string | null = null;. The | means "or". You will learn unions properly in Unions & Narrowing.
Common mistakes
- Annotating everything.
const total: number = 5 + 7;works, but the annotation adds nothing. Let inference do its job for simple local values. - Using
Stringinstead ofstring. The capital letter version is a different, rarely wanted type. - Expecting
intorfloat. TypeScript has onenumbertype for both. - Forgetting that types vanish at run time.
let age: number = Number(input)can still beNaNif the input is not a number. Check outside data with real code.
Key points
- Write a type annotation as
name: type, for examplelet marks: number = 88. - The primitive types are
string,number,boolean,bigint,symbol,nullandundefined, all lowercase. - Type inference works out types from values, so local variables with a value rarely need annotations.
- Annotate function parameters and variables that start without a value.
- In strict mode,
nullandundefinedmust be allowed explicitly, for example withstring | null.
Exercise
A cricket scorer in Rohtak tracks one innings. Declare variables for the team name, runs scored, wickets lost, whether the innings is complete, and the overs bowled (for example 19.4). Use inference where it is enough and an annotation where it is needed. Then declare let target: number;, give it the value runs + 1 and log a sentence such as Rohtak Rhinos: 186/7 in 20 overs. Target: 187.
Show solution
const team = "Rohtak Rhinos";
let runs = 186;
let wickets = 7;
let overs = 20;
let isComplete = true;
let target: number;
target = runs + 1;
console.log(`${team}: ${runs}/${wickets} in ${overs} overs. Target: ${target}`);
console.log("Innings complete:", isComplete);Every variable with a starting value is inferred. Only target needs an annotation, because it is declared before it has a value.
Quick Quiz
1. What type does TypeScript infer for let fee = 1299;?
A whole number literal without n is a number, and let keeps the general type.
2. Which annotation is correct for a name?
Primitive types are lowercase; String is the wrapper object type.
3. With strict mode on, what does let city: string = null; give?
Strict null checking keeps null out of string unless you write string | null.
4. When is an annotation most useful?
TypeScript cannot infer parameter types from callers or types for variables with no starting value.
Learn TypeScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
