DEV Community

Cover image for How to Use Type Annotations to Explicitly Type Variables in TypeScript
Krunal Kanojiya
Krunal Kanojiya

Posted on

How to Use Type Annotations to Explicitly Type Variables in TypeScript

TLDR

A type annotation tells TypeScript exactly what type a variable, function parameter, or return value should be. You write it using a colon after the name, like let age: number = 30. Annotations are removed when TypeScript compiles to JavaScript. They only exist to help you catch bugs during development.


What is a Type Annotation?

A type annotation is a label you attach to a variable or function. It tells TypeScript what kind of value is allowed there.

TypeScript can guess types in many situations on its own. But sometimes it cannot. And sometimes you want to be extra clear about what a variable should hold. That is when you write an annotation yourself.

The syntax is simple. You put a colon after the name, then write the type:

let age: number;
let name: string;
let isActive: boolean;
Enter fullscreen mode Exit fullscreen mode

The Basic Syntax

The pattern for a type annotation is always the same:

name: type
Enter fullscreen mode Exit fullscreen mode

You can add it to variables, constants, function parameters, and return values. Here is the syntax for each:

Where Syntax Example
Variable let name: type let score: number
Constant const name: type = value const title: string = "Admin"
Function parameter (param: type) (age: number)
Function return value (): type (): string
Array name: type[] let tags: string[]
Object name: { key: type } let user: { name: string }

How to Annotate Variables

The most common place to use annotations is on variables declared with let.

let username: string;
let score: number;
let isLoggedIn: boolean;
Enter fullscreen mode Exit fullscreen mode

Here, the variables are declared but not given a value yet. TypeScript knows their types from the annotation alone.

Later, when you assign a value, TypeScript checks that it matches:

username = "Alice";    // OK
username = 42;         // Error: Type 'number' is not assignable to type 'string'.

score = 100;           // OK
score = "one hundred"; // Error: Type 'string' is not assignable to type 'number'.
Enter fullscreen mode Exit fullscreen mode

You can also annotate and assign a value in one line:

let username: string = "Alice";
let score: number = 100;
let isLoggedIn: boolean = true;
Enter fullscreen mode Exit fullscreen mode

Annotating constants

const variables cannot be reassigned. You can still annotate them, but it is often not needed because TypeScript infers the type from the value.

const MAX_RETRIES: number = 3;    // Annotation added for clarity
const API_URL: string = "https://api.example.com";
const DEBUG: boolean = false;
Enter fullscreen mode Exit fullscreen mode

How to Annotate Function Parameters

Function parameters are the most important place to add type annotations. Without them, TypeScript cannot check if you call the function with the wrong values.

function greet(name: string): void {
  console.log("Hello, " + name);
}

greet("Alice"); // OK
greet(42);      // Error: Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Each parameter gets its own annotation:

function add(a: number, b: number): number {
  return a + b;
}

add(5, 10);    // OK — returns 15
add(5, "10");  // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Enter fullscreen mode Exit fullscreen mode

Multiple parameters with different types

function createUser(name: string, age: number, isAdmin: boolean): void {
  console.log(name, age, isAdmin);
}

createUser("Alice", 30, true);  // OK
createUser(30, "Alice", true);  // Error: wrong order of types
Enter fullscreen mode Exit fullscreen mode

How to Annotate Function Return Types

You write the return type after the closing parenthesis of the parameters, before the opening curly brace.

function getWelcomeMessage(name: string): string {
  return "Welcome, " + name;
}
Enter fullscreen mode Exit fullscreen mode

The : string after (name: string) tells TypeScript this function must return a string.

If your function returns the wrong type, TypeScript catches it:

function getAge(): number {
  return "thirty";
  // Error: Type 'string' is not assignable to type 'number'.
}
Enter fullscreen mode Exit fullscreen mode

The void return type

Use void when a function does not return anything:

function logMessage(message: string): void {
  console.log(message);
  // No return statement needed
}
Enter fullscreen mode Exit fullscreen mode

void tells TypeScript and other developers: "This function does something but gives nothing back."

Common return type annotations

Return Type When to Use
string Function returns text
number Function returns a numeric value
boolean Function returns true or false
void Function performs an action but returns nothing
string[] Function returns an array of strings
number[] Function returns an array of numbers

How to Annotate Arrays

To annotate an array, write the type followed by []:

let names: string[] = ["Alice", "Bob", "Charlie"];
let scores: number[] = [95, 87, 72];
let flags: boolean[] = [true, false, true];
Enter fullscreen mode Exit fullscreen mode

TypeScript checks both the array itself and every item inside it:

let names: string[] = ["Alice", "Bob"];
names.push(42);
// Error: Argument of type 'number' is not assignable to parameter of type 'string'.

names.push("David"); // OK
Enter fullscreen mode Exit fullscreen mode

The alternative Array<type> syntax

TypeScript also supports a second syntax for arrays using angle brackets:

let names: Array<string> = ["Alice", "Bob"];
let scores: Array<number> = [95, 87];
Enter fullscreen mode Exit fullscreen mode

Both styles do exactly the same thing. Most developers use type[] because it is shorter and easier to read.


How to Annotate Objects

To annotate an object, you write the shape of the object inside curly braces:

let user: { name: string; age: number; isAdmin: boolean };

user = { name: "Alice", age: 30, isAdmin: true };  // OK
user = { name: "Alice", age: 30 };                 // Error: missing 'isAdmin'
Enter fullscreen mode Exit fullscreen mode

TypeScript checks that the object has all the required properties with the right types.

Inline object annotation

You can write the annotation inline when you declare the variable:

let product: { id: number; title: string; price: number } = {
  id: 1,
  title: "TypeScript Book",
  price: 29.99
};
Enter fullscreen mode Exit fullscreen mode

Optional properties in objects

Add a ? after the property name to make it optional:

let profile: { username: string; bio?: string } = {
  username: "alice_dev"
  // bio is optional, so we can skip it
};
Enter fullscreen mode Exit fullscreen mode

Tip: For reusable object shapes, use a type alias or interface instead of writing the annotation inline every time. We cover those in later posts.


How to Annotate Union Types

Sometimes a variable can hold more than one type. Use a union type with the | symbol:

let id: string | number;

id = 101;       // OK
id = "abc_101"; // OK
id = true;      // Error: Type 'boolean' is not assignable to type 'string | number'.
Enter fullscreen mode Exit fullscreen mode

You can use union types on function parameters too:

function printId(id: string | number): void {
  console.log("ID:", id);
}

printId(1);       // OK
printId("abc");   // OK
Enter fullscreen mode Exit fullscreen mode

Note: We cover union types in detail in a later post. This is just a quick look at the annotation syntax.


How to Annotate with null and undefined

With strict: true, you need to be explicit when a value can be null or undefined.

let selectedItem: string | null = null;   // Can be a string or null
let cachedValue: number | undefined;      // Can be a number or undefined
Enter fullscreen mode Exit fullscreen mode

Without the | null or | undefined, TypeScript will not let you assign those values:

let name: string = null;
// Error: Type 'null' is not assignable to type 'string'.

let name: string | null = null; // OK
Enter fullscreen mode Exit fullscreen mode

What Happens to Annotations After Compilation?

Type annotations do not exist in JavaScript. When TypeScript compiles your code, it removes every annotation.

TypeScript (before compile):

let username: string = "Alice";
let score: number = 100;

function add(a: number, b: number): number {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

JavaScript (after compile):

let username = "Alice";
let score = 100;

function add(a, b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

All the : string, : number, and : boolean parts are stripped out. The output is clean JavaScript with no trace of the types.

This means type annotations have zero runtime cost. They only help you during development.


Annotation vs Inference: When to Write It Yourself

TypeScript can infer many types without annotations. So when should you write them yourself?

Write the annotation when:

  • You declare a variable without assigning a value right away
  • The return type of a function is not obvious
  • TypeScript infers a type that is too broad for your needs
  • You want to make the code easier to read for your team

Skip the annotation when:

  • The type is obvious from the assigned value
  • TypeScript can figure it out from context
// Skip annotation: value makes the type obvious
let name = "Alice";     // TypeScript infers: string
let count = 0;          // TypeScript infers: number
let active = true;      // TypeScript infers: boolean

// Write annotation: no value assigned yet
let username: string;
let retries: number;

// Write annotation: return type is not obvious from function name
function fetchData(): Promise<string> {
  return fetch("/api").then(res => res.text());
}

// Write annotation: function parameter always needs one
function double(n: number): number {
  return n * 2;
}
Enter fullscreen mode Exit fullscreen mode

Full Example Putting It All Together

Here is a small program that uses type annotations throughout:

// Variable annotations
let appName: string = "TaskManager";
let version: number = 1;
let isProduction: boolean = false;

// Array annotation
let taskTitles: string[] = ["Write blog", "Review PR", "Fix bug"];

// Object annotation
let currentUser: { id: number; name: string; email: string } = {
  id: 1,
  name: "Alice",
  email: "alice@example.com"
};

// Function with parameter and return type annotations
function getTaskCount(tasks: string[]): number {
  return tasks.length;
}

// Function with void return type
function logStatus(status: string): void {
  console.log("App status:", status);
}

// Union type annotation
let searchInput: string | number = "";

// Optional value with null
let activeTask: string | null = null;

// Using the annotated values
const count: number = getTaskCount(taskTitles);
logStatus("running");
console.log(`${appName} v${version}${count} tasks loaded`);
Enter fullscreen mode Exit fullscreen mode

Common Annotation Mistakes

Mistake 1: Using uppercase type names

let name: String = "Alice"; // Wrong — String is an object, not a primitive
let name: string = "Alice"; // Correct
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Forgetting the return type on public functions

// Unclear what this returns
function getUser(id: number) {
  return { id, name: "Alice" };
}

// Clearer with a return type
function getUser(id: number): { id: number; name: string } {
  return { id, name: "Alice" };
}
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Annotating when TypeScript can already infer the type

// Redundant annotation
let score: number = 100;

// Cleaner: TypeScript infers number from the value
let score = 100;
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Missing annotation on function parameters

// Bad: TypeScript has to guess what 'x' is
function square(x) {
  return x * x;
}

// Good: annotation makes the intent clear
function square(x: number): number {
  return x * x;
}
Enter fullscreen mode Exit fullscreen mode

FAQ

Q: Do I have to annotate every variable?
No. TypeScript can infer many types on its own. Annotate when TypeScript cannot figure out the type, or when you want to make your intent clear to other developers.

Q: Do type annotations affect runtime performance?
No. TypeScript removes all annotations during compilation. The output is plain JavaScript with no types. Annotations have zero impact on how fast your code runs.

Q: What is the difference between a type annotation and a type assertion?
A type annotation tells TypeScript what type something should be. A type assertion (as SomeType) forces TypeScript to treat a value as a specific type, even if it disagrees. Annotations are the safe, standard way. Assertions should be used with care.

Q: Can I use type annotations in JavaScript files?
No. Type annotations only work in .ts files. If you need type hints in .js files, you can use JSDoc comments, but that is a different approach.

Q: Should I annotate the return type of every function?
It is good practice for public functions and exported utilities. For small internal helper functions where the return type is obvious, you can let TypeScript infer it.

Q: What happens if I annotate the wrong type?
TypeScript shows a compile error. For example, if you annotate a variable as number but assign a string, you will see an error immediately. The code will not compile until the mismatch is fixed.


What You Learned

  • Type annotations use the : type syntax after a variable, parameter, or function name
  • You can annotate variables, constants, function parameters, return types, arrays, and objects
  • Annotations are removed during compilation and have no runtime cost
  • Use void for functions that do not return a value
  • Use type[] to annotate arrays
  • Use { key: type } to annotate objects inline
  • Use | to annotate union types when a value can be more than one type
  • Write annotations when TypeScript cannot infer the type or when you want to be explicit

Sources: TypeScript Handbook — Type Annotations | TypeScript Tutorial — Type Annotations

Top comments (0)