December 10, 2024 / 15 min read

Learning JavaScript vs. TypeScript: Understanding the Role of Types

JavaScriptTypeScript

⚓︎Introduction


JavaScript (JS) and TypeScript (TS) are two of the most widely used languages in modern web development. While JavaScript is the de facto standard for front-end development, TypeScript is gaining popularity due to its added features and type safety. But how do the two compare, especially when it comes to learning and working with types? Let’s dive in!

⚓︎What Are Types in Programming?


Types in programming define the kind of value a variable can hold. For instance, a variable can be a string, number, boolean, or other types.

  • JavaScript: JavaScript is a dynamically typed language. Variables can hold any type of value, and the type can change at runtime.
  • TypeScript: TypeScript is a statically typed superset of JavaScript. Types are explicitly declared or inferred during development, catching potential errors at compile time.

⚓︎Type Declaration: Adding Explicit Types in TypeScript


In TypeScript, you can explicitly declare the type of a variable, function, or parameter. This ensures type safety and improves code clarity.

Variable Declarations


  • In JavaScript, the type of data can change at runtime, making it flexible but potentially error-prone.
  • In TypeScript, assigning a number to data raises a compile-time error, ensuring type safety.
Loading...

Function Parameters


  • In JavaScript, it doesn’t enforce types, so unintended inputs can lead to errors or unexpected outputs.
  • In TypeScript, you specify that name must be a string, preventing invalid inputs.
Loading...

Object Properties


  • JavaScript allows dynamic changes to objects, which can lead to inconsistencies if properties are added incorrectly.
  • TypeScript enforces the structure of the object through the User interface, preventing accidental additions or changes.
Loading...

Arrays


  • In JavaScript, mixed-type arrays can cause bugs.
  • In TypeScript, the array is strictly typed, ensuring that only numbers can be added.
Loading...

Functions with Default Parameters


  • JavaScript does not enforce parameter types, leading to potential issues with type coercion.
  • TypeScript ensures that only numbers can be passed, avoiding errors caused by invalid types.
Loading...

Handling Null or Undefined Values


  • In JavaScript, functions do not handle null or undefined values by default.
  • In TypeScript, union types (string | null) allow handling multiple types explicitly, reducing runtime errors.
Loading...

Using Interfaces for Consistency


  • JavaScript allows missing properties, which can lead to inconsistent data.
  • TypeScript enforces consistent object structure using interfaces.
Loading...

⚓︎Type Inference: Let TypeScript Figure It Out


You don't always need to explicitly annotate types. TypeScript can infer types based on how values are used, reducing boilerplate while keeping safety.

  • JavaScript has no concept of inference — types are entirely dynamic.
  • TypeScript infers types from assigned values, so you write less but still get full type checking.
Loading...

Inference also shines with function return types:

Loading...

⚓︎Union Types and Type Narrowing


Real-world data isn't always one type. Union types let a value be one of several types, and type narrowing lets you handle each case safely.

  • JavaScript relies on manual checks with typeof or instanceof at runtime.
  • TypeScript models the possibility explicitly and forces you to handle all cases.
Loading...

This pattern is especially useful for API responses, form inputs, and configuration values that can vary.

⚓︎Generics: Reusable Type-Safe Code


Generics let you write functions, classes, and types that work with any type while preserving type safety. This is one of TypeScript's most powerful features.

  • JavaScript handles anything but provides zero guarantees about what comes back.
  • TypeScript with generics gives you flexibility AND safety at the same time.
Loading...

Another common example — a typed identity function or generic state container:

Loading...

Generics are the foundation of modern TypeScript libraries and frameworks — from React's useState<T>() to API client builders.

⚓︎Utility Types: Transforming Types on the Fly


TypeScript ships with built-in utility types that let you derive new types from existing ones without rewriting definitions.

UtilityDescriptionExample
Partial<T>Makes all properties optionalPartial<User>
Pick<T, K>Selects specific propertiesPick<User, 'id' | 'name'>
Omit<T, K>Excludes specific propertiesOmit<User, 'password'>
Readonly<T>Makes all properties read-onlyReadonly<Config>
Loading...

These utilities reduce duplication, prevent bugs when types change, and make refactoring significantly safer.

⚓︎Type Aliases vs Interfaces


Both type and interface define object shapes, but they have key differences in capability and style.

Featureinterfacetype
Extendingextends keywordIntersection (&)
Declaration merging✅ Can be re-declared❌ Cannot be re-declared
Unions / Primitives❌ Cannot represent✅ Can alias any type
Loading...

Use interface when defining public API shapes and object contracts that might be extended. Use type for unions, primitives, tuples, and when you need computed properties.

⚓︎Enums: Named Constants


Enums let you define a set of named constants, making code more readable and self-documenting.

  • JavaScript has no built-in enum concept — developers use objects or plain strings.
  • TypeScript provides both numeric and string enums.
Loading...

String enums are preferred for readable debugging, while numeric enums are useful for flags and bitwise operations.

⚓︎The unknown vs any Types


TypeScript provides two "escape hatch" types that behave very differently. Understanding the distinction is crucial for writing safe code.

TypeCan assign anything to it?Can use it without checking?
any✅ Yes✅ Yes — disables all checking
unknown✅ Yes❌ No — must narrow first
Loading...

Rule of thumb: Never use any for API responses, user input, or data from external sources. Use unknown and narrow — it forces you to validate before use.

⚓︎Discriminated Unions


When a value can be one of several shapes, a discriminated union uses a common property (the "discriminant") to distinguish between them.

  • JavaScript requires verbose runtime checks for each case.
  • TypeScript narrows the type automatically based on the discriminant.
Loading...

This is the go-to pattern for modeling API responses, state machines, form steps, and any data that has multiple distinct shapes.

⚓︎Type Assertions


Sometimes you know more about a value's type than TypeScript does. Type assertions tell the compiler to trust your judgement — use them sparingly.

  • JavaScript has no concept of type assertions.
  • TypeScript uses the as keyword to assert a type.
Loading...

Assertions don't perform runtime checks — they only tell the compiler to trust you. When possible, prefer runtime validation (narrowing, type guards) over assertions.

Use assertions when:

  • Interacting with DOM APIs that TypeScript can't infer precisely
  • Parsing data you've already validated at runtime
  • Working with third-party libraries with incomplete types

⚓︎Transitioning from JavaScript to TypeScript


If you're a JavaScript developer, transitioning to TypeScript can be done gradually:

  • Start with .js files: Rename them to .ts files.
  • Enable noImplicitAny: Configure TypeScript to enforce typing where it’s ambiguous.
  • Adopt Gradually: Add types only to critical parts of your codebase.

⚓︎Conclusion


JavaScript’s flexibility makes it an excellent choice for beginners and rapid prototyping. However, as projects grow in complexity, TypeScript's type safety and advanced features become invaluable for maintaining code quality.

Type declarations and interfaces are two of the most powerful tools in TypeScript, making your code robust, clear, and easier to debug. By gradually incorporating TypeScript into your workflow, you can unlock the benefits of static typing without losing the dynamic capabilities of JavaScript.