Common TypeScript Beginner Mistakes

New TypeScript developers make the same mistakes: skipping parameter types, overusing any, and forgetting that types disappear at runtime. Learn to spot and fix the most frequent errors.

8 min read

Common TypeScript beginner mistakes tend to repeat across nearly every new codebase, even though TypeScript itself is designed to catch mistakes early. The good news is that these patterns are well known, easy to spot, and simple to fix once you understand what the compiler is actually telling you.

Here are the seven most common mistakes new TypeScript developers make, why each one is a problem, and exactly how to fix it.

1. Forgetting to Annotate Function Parameters

This is the number one mistake. Function parameters are the one place TypeScript cannot infer types, because there is no value to look at when the function is declared.

typescripttypescript
function add(a, b) {
  return a + b;
}
 
add(5, 10);   // 15
add("5", 10); // "510" (no error, but probably a bug)

Without annotations, both parameters become any, so the function accepts anything and the string-concatenation bug above slips through with no warning at all. Annotating both parameters closes the gap and gives the compiler something to check calls against:

typescripttypescript
function add(a: number, b: number): number {
  return a + b;
}
 
add("5", 10);

Calling it with a string in the first position now fails to compile instead of quietly producing the wrong result, because the annotation gives the compiler something concrete to check the argument against:

texttext
Argument of type 'string' is not assignable to parameter of type 'number'.

Always annotate function parameters. Enabling the noImplicitAny compiler flag turns every untyped parameter into a compile error automatically, so the mistake cannot ship even if someone forgets.

2. Using Any as a Shortcut

When TypeScript complains about a type, the quickest fix is to annotate it as any and move on. This works, but it disables all type checking for that value and everything it touches.

typescripttypescript
function processOrder(order: any) {
  console.log(order.total.toFixed(2)); // crashes if total is missing
  order.sendEmail();                   // crashes if sendEmail does not exist
}

The any type spreads: once a value passes into another function or expression, the result becomes any too, so a single loose parameter can quietly disable checking across an entire module. Defining the actual shape closes that hole:

typescripttypescript
type Order = {
  total: number;
  items: string[];
};
 
function processOrder(order: Order) {
  console.log(order.total.toFixed(2)); // safe: total is guaranteed to be a number
}

If you genuinely do not know the shape of the data, use unknown instead. The compiler forces you to narrow the type before you can use it, which keeps the code safe without giving up on checking entirely. For a full comparison of the two, see any vs unknown in TypeScript.

3. Not Enabling Strict Mode

TypeScript's most valuable safety checks are opt-in. Without strict mode enabled in the compiler configuration, the compiler lets through patterns that cause runtime errors:

jsonjson
{
  "compilerOptions": {
    "strict": true
  }
}

The table below shows what strict mode enables and what happens without it:

Strict flagWhat it catchesWithout it
strictNullChecksUsing a value that might be nullnull silently passes as any type
noImplicitAnyMissing parameter typesParameters default to any
strictFunctionTypesUnsound function type checksWeaker checks on function parameters
noImplicitThisthis used without a typethis is implicitly any

Without strictNullChecks, a missing value flows straight through to a method call and only fails once the code actually runs:

typescripttypescript
let name: string = null;
console.log(name.toUpperCase());

With it enabled, the exact same line never compiles in the first place, so the crash above simply cannot reach production:

texttext
Type 'null' is not assignable to type 'string'.

Start new projects with strict mode on from day one. For existing JavaScript projects migrating to TypeScript, enable strict flags one at a time. For more on the strict mode flags, see strict mode in TypeScript.

4. Using Wrapper Types Instead of Primitives

JavaScript has wrapper objects String, Number, and Boolean that are different from the primitive values string, number, and boolean. Using the capitalized versions as types refers to the wrapper objects, not the primitives.

typescripttypescript
function greet(name: String): String {
  return `Hello, ${name}`;
}

This compiles, but capital String refers to the wrapper object type, not the primitive, and it causes subtle problems: a plain string is assignable to the wrapper type, but not the other way around, so the two are not fully interchangeable once mixed.

typescripttypescript
function greet(name: string): string {
  return `Hello, ${name}`;
}

Always use the lowercase primitive names for type annotations. There is never a good reason to reach for the capitalized wrapper types in ordinary application code.

5. Assuming Types Exist at Runtime

TypeScript types are erased during compilation. They do not exist when your code runs. This is the most fundamental thing to understand about TypeScript, and it trips up beginners regularly.

typescripttypescript
type Cat = { meow: () => void };
type Dog = { bark: () => void };
 
function handlePet(pet: Cat | Dog) {
  if (pet instanceof Cat) {
    pet.meow();
  }
}

The instanceof operator checks a runtime value, but Cat is a compile-time type with no trace left in the compiled output, so the check itself fails before the code can even run:

texttext
'Cat' only refers to a type, but is being used as a value here.

The fix is to check for a property that actually exists at runtime instead of reaching for the type itself, since only real JavaScript values survive into the compiled output:

typescripttypescript
function handlePet(pet: Cat | Dog) {
  if ("meow" in pet) {
    pet.meow(); // narrowed to Cat
  } else {
    pet.bark(); // narrowed to Dog
  }
}

The in operator checks for the existence of a property at runtime, and TypeScript uses that check to narrow the type.

The same rule applies to type assertions written with the as keyword: they compile cleanly but add no runtime safety, so casting a parsed API response to a specific shape can still crash if the actual response changes. For data that comes from outside your program, use runtime validation. For more on this pattern, see runtime validation in TypeScript.

6. Over-Annotating Variables

TypeScript can infer types from initial values. Adding annotations on top of inference is redundant and creates extra maintenance work.

typescripttypescript
let title: string = "Learn TypeScript";
let count: number = 0;
let items: string[] = ["a", "b", "c"];

Every annotation here repeats what the initial value already says, and if the value's type ever changes, the annotation needs a manual update too. Removing the redundant annotations keeps the type accurate automatically:

typescripttypescript
let title = "Learn TypeScript";   // inferred: string
let count = 0;                    // inferred: number
let items = ["a", "b", "c"];      // inferred: string[]

Add annotations where inference does not work: function parameters, variables declared without an initial value, empty arrays, and places where you want a narrower type than inference provides.

7. Confusing Void and Undefined Return Types

A function that returns nothing should use void, not undefined. The two types have different meanings.

typescripttypescript
function log(message: string): undefined {
  console.log(message);
}

Annotating undefined as a return type means the function must explicitly return that value, so a function that just logs and falls off the end fails to compile:

texttext
A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.

void means the function returns nothing the caller should use, and it does not require an explicit return statement at all:

typescripttypescript
function log(message: string): void {
  console.log(message); // ok: no return statement needed
}

It is the standard type for event handlers, callbacks, setters, and any function whose purpose is a side effect rather than producing a value. For the full distinction, see void and never in TypeScript.

Rune AI

Rune AI

Key Insights

  • Always annotate function parameters. TypeScript cannot infer them, and untyped parameters become any.
  • Enable strict mode from the start. It catches the most dangerous patterns before they become habits.
  • Use unknown instead of any for values from external sources like API responses and user input.
  • Use lowercase string, number, boolean for type annotations, never the capitalized wrapper objects.
  • Types are erased at compile time. Use runtime validation for data that comes from outside your program.
  • Let TypeScript infer types on initialized variables. Adding annotations where inference works is just noise.
RunePowered by Rune AI

Frequently Asked Questions

Why are my function parameters `any` when I did not annotate them?

TypeScript cannot infer parameter types because there is no value to look at when the function is declared. Without explicit annotations or strict mode, parameters default to `any`. Always annotate function parameters, and enable `noImplicitAny` in your tsconfig to make untyped parameters an error.

Should I always add return type annotations to functions?

Not always. TypeScript infers return types accurately for simple functions. Add an explicit return type when the function is exported, when the return type is not obvious, or when you want the compiler to catch mistakes inside the function body.

Is it bad to use `any` during development?

Using `any` to move quickly during development is common, but it creates hidden bugs. When you do use `any`, add a comment explaining why and plan to replace it with a proper type before merging. The `unknown` type is a safer temporary placeholder.

Conclusion

Every TypeScript beginner hits the same walls: untyped parameters, the temptation of any, confusion about compile-time versus runtime, and the wrapper type trap. The fixes are straightforward. Annotate every function parameter. Use unknown instead of any. Enable strict mode from day one. Use lowercase primitive types. And remember: types are a compile-time safety net, not a runtime guarantee. Fixing these patterns early builds the right habits and prevents countless bugs down the line.