JavaScript's Problems and How TypeScript Solves Them

7 min read

You finished the JavaScript for QA course. You can read a Cypress test, write a small utility, debug a stack trace. So why are job postings, framework docs, and your team's repository all asking for TypeScript? Because once a test suite grows past a few files, JavaScript's flexibility starts costing more than it gives — and TypeScript fixes the most painful parts without changing the language you already know.

A real bug JavaScript lets through

Here's a test helper from a Playwright project. It logs a user in and returns their auth token.

function login(user) {
  return api.post("/login", {
    email: user.email,
    password: user.password,
  });
}
 
// Used elsewhere:
login("alice@example.com");  // ❌ pass a string instead of an object

JavaScript runs this happily. The function reaches user.email and gets undefined (strings have no .email property), the API receives { email: undefined, password: undefined }, the server returns 400, and your test fails three steps later with a confusing "Cannot read response of failed login." You debug for twenty minutes before noticing that someone passed the wrong argument shape.

Here's the same code in TypeScript:

function login(user: { email: string; password: string }) {
  return api.post("/login", {
    email: user.email,
    password: user.password,
  });
}
 
login("alice@example.com");
// ❌ Argument of type 'string' is not assignable to parameter of type
//    '{ email: string; password: string; }'.

Your editor underlines the call in red before you save the file. The compiler refuses to build the project. The bug is caught the moment it's typed — not in CI at midnight.

The four problems TypeScript fixes

JavaScript's flexibility is the whole reason it's everywhere. It's also the reason large test suites rot. TypeScript targets four specific pain points:

  1. No type checking. function login(user) {} — what is user? A string? An object? An object with which fields? JavaScript shrugs. Every caller has to read the function body to find out, and gets it wrong eventually.
  2. Runtime errors instead of compile-time errors. Bugs only surface when the code actually executes. A typo in response.bdy.userId (instead of body) sits in your test until that branch runs — sometimes weeks later, in CI, on a flaky-looking failure.
  3. Poor IDE support. Autocomplete doesn't know what fields an object has, so you guess. Misremember a property name and JavaScript silently returns undefined and keeps going.
  4. Refactoring is dangerous. Rename a field in your test data factory and you have to grep — carefully — for every usage. Miss one and the test still passes locally because JavaScript doesn't care; it breaks for someone else next week.

What TypeScript actually is

TypeScript is a superset of JavaScript. Every valid .js file is a valid .ts file. TypeScript adds an optional type layer on top — annotations like : string, : User[], : Promise<AuthToken> — and a compiler that checks them.

Browsers and Node.js don't run TypeScript directly. The compiler (tsc) checks your types and emits plain JavaScript, which is what actually executes. The types are a development-time tool. At runtime, your TypeScript code is regular JavaScript with the type annotations stripped out.

If you know JavaScript, you already know about 90% of TypeScript. You're just learning to add : type after variable names, parameters, and return values.

What you get back

The same bug — JavaScript vs TypeScript

JavaScript

  • Bug ships to CI

  • Test fails with confusing error 3 steps later

  • 20 minutes of console.log debugging

  • Editor offers no autocomplete on user.*

  • Renaming fields = grep-and-pray

TypeScript

  • Red squiggle in the editor as you type

  • Compiler refuses to build the project

  • Error message names the wrong type exactly

  • Autocomplete shows email, password, role…

  • Rename a field — every caller updated

The four wins, concretely:

  1. Catch bugs before tests run. A wrong property name, a missing argument, a mismatched type — the compiler flags it the moment you save.
  2. Real autocomplete. Type user. and see the actual fields the User interface declares. No more guessing whether it's userName or username.
  3. Self-documenting code. function login(user: User): Promise<AuthToken> tells you exactly what goes in and what comes out — no need to read the body.
  4. Safe refactoring. Rename a field on the User interface, and the compiler walks the whole project pointing at every line that needs updating. Refactors stop being scary.

Why test frameworks recommend it

Cypress's official starter projects use TypeScript by default. Playwright's init wizard offers TypeScript first. Jest, Vitest, and most modern API testing libraries ship type definitions in the box. The reason is the same one that pushed product engineering to TypeScript years ago: as a project grows, the cost of "we're not sure what shape this object is" compounds. TypeScript pays the small upfront cost of annotations to avoid a much larger cost later.

The good news for you: if you're comfortable with the JavaScript for QA cheat sheet, the VS Code editor, and the basic shape of an assertion, every JavaScript skill you have transfers directly. You're learning a small set of additions, not a new language.

⚠️ Common mistakes

  • Thinking TypeScript is a different language. It isn't. Every JS file you wrote in the previous course is already legal TypeScript. The skill is adding annotations, not replacing what you know.
  • Trying to type everything immediately. TypeScript supports gradual adoption — you can type one function, one file, one folder at a time. Beginners often try to annotate every variable and burn out. Annotate function parameters and return types first; let inference handle the rest.
  • Ignoring red squiggles. A red squiggle in VS Code is the compiler refusing to ship broken code. New TypeScript users sometimes silence them with // @ts-ignore and move on. That defeats the entire point — you've just turned TypeScript back into JavaScript.

🎯 Practice task

No code yet — you'll write your first TypeScript file in lesson 3. For now, 15 minutes of recognition. The next lesson installs TypeScript; this one builds the mental model.

  1. Open the most complex JavaScript file you wrote in JavaScript for QA — the test report script, the JSON fixture parser, the API helper.
  2. Find every function in that file. For each parameter, write a one-line comment above the function describing the type you expect — // user is { email: string, password: string }, // retries is a positive number, // onComplete is a callback that returns void.
  3. Find every console.log of an object. Ask: "if I typo a property name here, would JavaScript tell me?" (It wouldn't.)
  4. Find one place where, if you renamed a property on an object, you'd have to grep across the file to update every usage. Note how many places need to change.

You've just identified — by hand — the work TypeScript will do for you automatically. The next lesson installs the compiler and gets you set up to feel the difference yourself.

// tip to track lessons you complete and pick up where you left off across devices.