⏳ JavaScript + TypeScript Fundamentals

Understanding the core building blocks of JavaScript and TypeScript helps you write better code, debug faster, and build more maintainable apps. This guide covers the must-know fundamentals including variable scopes, hoisting, closures, types, classes, and more.
🔁 var vs let vs const
JavaScript gives us three ways to declare variables:
var: the oldest, function-scoped, hoisted
let: block-scoped, reassignable
const: block-scoped, but cannot be reassigned
var car = "car"; // function scoped
let a = 5; // block scoped
const b = 1; // cannot reassign
🌀 Scope, Hoisting, Closures
Scope is where variables live and how accessible they are.
Hoisting is how var and function declarations are "lifted" to the top of their scope before code runs.
Closures allow inner functions to "remember" and access variables from their outer function, even after that outer function has finished executing.
🧙♀️ Hoisting in JavaScript
Definition: Hoisting is when JavaScript "remembers" all your variable and function declarations before running your code.
Memory allocation phase:
- All var, function, and class declarations are stored in memory automatically before execution.
Key insights:
Only declarations are hoisted, not initializations.
let and const are hoisted but are not accessible before declaration.
Function declarations are hoisted fully. Function expressions are not.
🧳 Closures
A closure is when an inner function remembers variables from its outer function - even after the outer function is done.
Closures are not unique to JavaScript, but in JS, they are extremely useful and common. When you declare a function, it captures the scope in which it was defined - and remembers it.
const name = "Olga";
export function sayHello() {
console.log(name);
}
Even if someone imports and runs sayHello, and doesn’t provide an argument, it will still find and use the name variable. That’s closure in action.
Here’s a more dynamic example using a function returning another function:
export function createSayHello() {
const name = "Olga";
return function sayHello() {
console.log(name);
}
}
const sayHelloOlga = createSayHello();
sayHelloOlga(); // logs "Olga"
You get not just the function - but the entire scope it was created in. That's the lexical environment 🌍
⚠️ Drawback of Closures
Closures put pressure on memory 🧠. All variables within the scope are still referenced as long as the function is used. This means:
They cannot be garbage collected
JavaScript cannot clean up that memory
The more closures you create, the more memory is retained
This is part of why JavaScript is often criticized for being less memory-efficient — it needs more RAM 💾.
📐 Types, Interfaces, Generics (TypeScript)
🧩 Types
let age: number = 25;
let name: string = "Olga";
let isAdmin: boolean = true;
🧷 Interfaces
interface User {
id: number;
name: string;
}
const user: User = { id: 1, name: "Olga" };
🎁 Generics
function wrapInArray<T>(value: T): T[] {
return [value];
}
const nums = wrapInArray<number>(5); // [5]
📦 Objects, Arrays, Maps, Sets
🧱 Objects — Key-value pairs
const user = { name: "Alex", age: 22 };
📋 Arrays — Ordered collections
const numbers = [1, 2, 3];
🗺️ Maps — Key-value store with any type as key
const map = new Map();
map.set("a", 1);
🔢 Sets — Only unique values allowed
const set = new Set([1, 2, 2, 3]); // {1, 2, 3}
🏛️ Classes, Inheritance, this
class Animal {
constructor(public name: string) {}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}
const d = new Dog("Rex");
d.speak(); // Rex barks
🔁 this Keyword
Refers to the current context (object, function, or global)
Arrow functions do not have their own
this— they inherit from the parent scope
Common Gotchas & Tips
✅ Prefer const by default. Use let only when needed.
❌ Don’t use == — use === for strict equality
✅ Use optional chaining ?. to safely access nested properties
✅ Use destructuring for cleaner code
❌ Avoid mutating arrays/objects directly — prefer immutability


