Modern JavaScript Essentials Before Learning TypeScript
When experienced developers decide to learn TypeScript, many immediately jump into:
interfaces
generics
advanced typing
I made the same mistake.
The real problem wasn’t TypeScript — it was my JavaScript mental model.
TypeScript assumes you already write modern JavaScript.
If your JavaScript foundation is outdated, TypeScript feels unnecessarily complex.
This post documents the exact JavaScript concepts I mastered before starting TypeScript.
Why Modern JavaScript Matters
TypeScript is not a new runtime language.
It is:
JavaScript + Static Type System
TypeScript does not replace JavaScript — it analyzes it.
So before learning TypeScript, we must first write JavaScript the way TypeScript expects.
Setup (Follow Along)
Create a playground project.
mkdir js-to-ts
cd js-to-ts
npm init -y
touch index.js
node index.js
We will evolve this file step-by-step.
- const and let — Understanding Mutability Older JavaScript relied heavily on var. Modern JavaScript does not.
const name = "Ali";
let age = 30;
Why var Is Problematic
if (true) {
var x = 10;
}
console.log(x); // 10 😬
var ignores block scope.
Now compare:
if (true) {
let y = 10;
}
console.log(y); // ReferenceError ✅
Senior Developer Rule
✅ Use const by default
✅ Use let only when reassignment is required
const user = { name: "Ali" };
user.name = "Ahmed"; // allowed
user = {}; // ❌ not allowed
👉 const protects references, not values.
Why This Matters for TypeScript
TypeScript performs better type inference when variables are stable.
const status = "active";
// inferred as literal type "active"
Using let here would weaken the type.
Exercise
Refactor any old JS file:
remove all var
replace with const or let
- Arrow Functions — The Default Function Style Modern JavaScript prefers arrow functions.
const greet = (name) => `Hello ${name}`;
Function Forms Comparison
Traditional Function
function add(a, b) {
return a + b;
}
Arrow Function
const add = (a, b) => a + b;
Cleaner. Predictable. Preferred in TS projects.
The Real Reason: this
Traditional functions redefine this.
class Counter {
count = 0;
increment() {
setTimeout(function () {
console.log(this.count); // undefined 😬
}, 1000);
}
}
Arrow functions capture surrounding this.
setTimeout(() => {
console.log(this.count); // ✅
});
Why TypeScript Cares
Many TypeScript errors originate from incorrect this binding.
Understanding arrow functions prevents hours of confusion later.
Exercise
Rewrite all callback functions using arrows.
- Destructuring — The Language of Modern APIs Instead of:
const user = { id: 1, name: "Ali" };
const id = user.id;
const name = user.name;
Use destructuring:
const { id, name } = user;
Function Parameters Destructuring
function printUser({ name, age }) {
console.log(name, age);
}
This pattern appears everywhere in TypeScript and React.
Arrays
const numbers = [10, 20];
const [first, second] = numbers;
Why TypeScript Loves This
TypeScript types data shapes.
Destructuring aligns perfectly with interface-based typing.
Exercise
Take an API response object and destructure all used fields.
- Spread Operator — Immutability Without Pain Instead of mutation:
user.active = true;
Prefer immutable updates:
const updatedUser = { ...user, active: true };
Copy Arrays
const nums = [1, 2, 3];
const newNums = [...nums, 4];
Merge Objects
const defaults = { role: "user" };
const admin = { ...defaults, role: "admin" };
Why This Matters for TypeScript
Immutability makes types predictable.
Mutable data causes complex type narrowing issues later.
Exercise
Refactor any mutation-heavy logic using spreads.
- ES Modules — How Modern Code Is Organized Old JavaScript:
require("./math");
Modern JavaScript:
// math.js
export function add(a, b) {
return a + b;
}
// index.js
import { add } from "./math.js";
Default Export
export default function multiply() {}
import multiply from "./math.js";
Why This Matters for TypeScript
TypeScript builds entirely on ES Modules.
If modules confuse you, TypeScript projects will too.
Exercise
Split your project into multiple files using imports/exports.
- Async/Await — Mandatory Before TypeScript Callbacks → Promises → Async/Await. Modern JavaScript uses async/await almost exclusively.
async function loadUser() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
);
return response.json();
}
Usage:
loadUser().then(console.log);
Error Handling
try {
const user = await loadUser();
} catch (error) {
console.error(error);
}
Why This Matters for TypeScript
TypeScript explicitly types async results:
Promise<User>
If Promises feel unclear, TypeScript will feel overwhelming.
Exercise
Convert a .then() chain into async/await.
- Classes — The Bridge Into TypeScript JavaScript classes are syntactic sugar over prototypes.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello ${this.name}`;
}
}
Inheritance
class Admin extends User {
deleteUser() {}
}
Why TypeScript Enhances Classes
TypeScript adds:
access modifiers
typed properties
interfaces
contracts
But the foundation is pure JavaScript.
Exercise
Create a small domain model using classes:
User
Admin
Guest
What You Can Safely Ignore
Before TypeScript, you do NOT need:
deep prototype chains
ES5 patterns
callback hell
browser internals
Phase 1 Checklist
You are ready for TypeScript if you can comfortably:
✅ use const and let correctly
✅ write arrow functions
✅ destructure objects
✅ work with modules
✅ use async/await
✅ understand classes
What Changed for Me
After mastering modern JavaScript, TypeScript stopped feeling like a new language.
It felt like JavaScript finally growing up.
Next post: Understanding the TypeScript Mental Model.
Top comments (0)