Inference and the Primitive Types
Let the compiler work out types for you, and learn the handful of primitives you will annotate by hand.
What you'll learn
- Predict the type TypeScript infers from an initial value
- Decide when an explicit annotation helps and when it is noise
- Explain why `let` and `const` infer differently
Prerequisites
Explanation
Beginners often assume TypeScript means annotating everything. It does not. The compiler infers a type from the value you assign, and an annotation that merely repeats what it already worked out is noise:
const title: string = "Intro"; // the ": string" adds nothing
const title = "Intro"; // already known to be a string
Inference is not a weaker form of typing. Both lines above are equally protected — assigning a number to title is rejected either way. The difference is only how much you had to type.
The primitives
string, number, and boolean cover the overwhelming majority of annotations. TypeScript has a single number for integers and decimals, exactly like JavaScript. There is no separate integer type.
Two more matter early:
nullandundefinedare their own types. Understrictmode (which this lab uses, and which you should use) they are not silently allowed everywhere — astringcannot holdnullunless you say so. Lesson 6 covers how to say so.anyopts out of checking entirely. A value typedanyaccepts anything and permits anything, which means every guarantee stops at its boundary. Reach for it rarely and deliberately.
Why let and const infer differently
This surprises people:
let status = "active"; // inferred: string
const level = "active"; // inferred: "active"
A const can never be reassigned, so the compiler knows its value will always be exactly "active" — it infers the literal type "active", narrower than string. A let may be reassigned later, so TypeScript widens the inference to string to leave room.
That distinction looks academic now and becomes genuinely useful in lesson 10, where literal types are what make a set of allowed values enforceable.
Where annotations earn their place
Annotate when there is no value to infer from, or when you want to constrain rather than describe:
let attempts: number; // no initial value — nothing to infer from
function f(count: number) {} // parameters are never inferred from the outside
Function parameters always need annotations. TypeScript cannot see the call sites while checking the function body, so it has nothing to infer from.
Example
Inference in action. Hover-free proof: reassigning to a contradicting type is rejected.
const courseName = "TypeScript Foundations"; // inferred as the literal "TypeScript Foundations"
let enrolled = 0; // inferred as number
let isPublished = true; // inferred as boolean
enrolled = enrolled + 1;
isPublished = false;
console.log(courseName, enrolled, isPublished);
// Rejected by the compiler — uncomment to see the error:
// enrolled = "three";Try it yourself
Uncomment the last line and Run. The error names both the type you supplied and the one expected.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Without writing any annotations, declare `siteName` as a const string, `visitorCount` as a let number, and `isLive` as a let boolean. Then increment `visitorCount` by 5 and log all three.
Checks: siteName is a string · visitorCount is a number · isLive is a boolean
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Declare `maxRetries` with an explicit `number` annotation but no initial value, then assign it 3. Also write `describeAttempt(attempt: number): string` that returns `"Attempt 3 of 3"` style text using `maxRetries`.
Checks: maxRetries is 3 · describeAttempt is defined · describeAttempt(1) returns "Attempt 1 of 3"
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Annotating every variable out of habit. If the initial value already tells TypeScript the type, the annotation only adds maintenance.
- Expecting `const greeting = "hi"` to infer `string`. It infers the literal type `"hi"` — narrower, and useful later.
- Using `any` to silence an error. It does not fix the mismatch; it removes checking from that point onward.
Knowledge check
Takeaway
Let inference do the work; annotate where there is nothing to infer from, especially function parameters.
Summary
TypeScript infers types from initial values, widening for `let` and narrowing to literal types for `const`. Annotations earn their place on parameters and on declarations with no initializer. `any` switches checking off and should be rare.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.