beginner20 min

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:

  • null and undefined are their own types. Under strict mode (which this lab uses, and which you should use) they are not silently allowed everywhere — a string cannot hold null unless you say so. Lesson 6 covers how to say so.
  • any opts out of checking entirely. A value typed any accepts 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.

Loading editor…

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.

Loading editor…

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.

Loading editor…

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

Knowledge check

1. What type does TypeScript infer for `const mode = "dark"`?
2. Which of these genuinely requires an explicit annotation?
3. What is the practical effect of typing a value as `any`?

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.