JavaScript is the only language that runs natively in every browser on the planet — and with Node.js it runs on servers too. Whether you want to build websites, web apps, mobile apps, or backend APIs, JavaScript is the universal starting point. This roadmap shows you exactly what to learn, in what order, and how long each phase realistically takes in 2025.
At a glance
| Phase | Topics | Time estimate |
|---|---|---|
| 1 | The web and your tools | 1 week |
| 2 | JavaScript fundamentals | 6–8 weeks |
| 3 | The DOM and the browser | 3–4 weeks |
| 4 | Asynchronous JavaScript | 3–4 weeks |
| 5 | Modern JavaScript (ES6+) | 2–3 weeks |
| 6 | Package ecosystem and build tools | 1–2 weeks |
| 7 | A front-end framework (React) | 6–8 weeks |
| 8 | Back-end with Node.js | 4–6 weeks |
| 9 | Databases | 3–4 weeks |
| 10 | Testing | 2–3 weeks |
| 11 | Deployment and DevOps basics | 2–3 weeks |
| Total | From zero to job-ready | ~12–16 months |
Phase 1 — The web and your tools (1 week)
Before writing a single line of JavaScript, get comfortable with the environment.
Browser developer tools — open Chrome DevTools with F12. You will live here.
Console tab → run JS snippets, see errors
Network tab → inspect HTTP requests and responses
Elements tab → inspect and live-edit HTML/CSS
Sources tab → set breakpoints and debug JS
VS Code essentials — install these extensions:
- ESLint — catches errors as you type
- Prettier — auto-formats your code on save
- GitLens — shows git blame inline
How JavaScript fits in the stack:
HTML → structure (what is on the page)
CSS → style (how it looks)
JS → behaviour (what it does)
JavaScript code can live inline in a <script> tag or in a separate .js file linked with <script src="app.js" defer></script>. Always prefer the separate file — it keeps concerns separated and lets the browser cache the script.
Phase 2 — JavaScript fundamentals (6–8 weeks)
This phase is the foundation. Do not rush it.
Variables and types
// Prefer const by default, use let when you need to reassign, never use var
const name = "Alice";
let count = 0;
// Primitive types
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" (infamous JS quirk)
typeof {} // "object"
typeof [] // "object" (arrays are objects)
typeof function(){} // "function"
Operators and control flow
// Equality — always use === (strict), never == (loose)
0 == false // true ← dangerous
0 === false // false ← correct
// Ternary
const label = count > 0 ? "positive" : "not positive";
// Optional chaining (ES2020) — avoids TypeError on null/undefined
const city = user?.address?.city; // undefined instead of crash
// Nullish coalescing — only falls back on null/undefined, not 0 or ""
const port = config.port ?? 3000;
Functions
// Function declaration — hoisted, can be called before definition
function add(a, b) {
return a + b;
}
// Arrow function — shorter, lexical `this`
const multiply = (a, b) => a * b;
// Default parameters
function greet(name = "World") {
return `Hello, ${name}!`;
}
// Rest parameters
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
// Spread operator
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
Arrays — the most important data structure in JS
const fruits = ["apple", "banana", "cherry"];
// Immutable transformations (learn these deeply)
fruits.map(f => f.toUpperCase()) // new array, transformed
fruits.filter(f => f.length > 5) // new array, filtered
fruits.reduce((acc, f) => acc + f, "") // single value
fruits.find(f => f.startsWith("b")) // first match
fruits.some(f => f === "apple") // boolean — any match?
fruits.every(f => f.length > 3) // boolean — all match?
fruits.flat() // flatten nested arrays
fruits.flatMap(f => [f, f.length]) // map + flat in one pass
// Mutation methods (use sparingly)
fruits.push("date") // add to end
fruits.pop() // remove from end
fruits.splice(1, 1) // remove 1 item at index 1
Objects
const user = {
name: "Alice",
age: 30,
address: { city: "London" }
};
// Destructuring
const { name, age } = user;
const { address: { city } } = user;
// Spread to clone or merge (shallow)
const updated = { ...user, age: 31 };
// Object methods
Object.keys(user) // ["name", "age", "address"]
Object.values(user) // ["Alice", 30, {...}]
Object.entries(user) // [["name","Alice"], ["age",30], ...]
Scope, closures, and this
| Concept | What it means |
|---|---|
| Global scope | Variables declared outside any function |
| Function scope | var is trapped inside its enclosing function |
| Block scope | let and const are trapped inside {} |
| Closure | A function that remembers variables from its outer scope |
this |
In regular functions: the calling context. In arrow functions: inherited from the enclosing scope |
// Closure — counter factory
function makeCounter() {
let count = 0; // private state
return () => ++count; // the returned function closes over `count`
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Phase 3 — The DOM and the browser (3–4 weeks)
The Document Object Model is the JavaScript API for reading and updating a live web page.
// Selecting elements
const btn = document.querySelector("#submit-btn"); // CSS selector, first match
const items = document.querySelectorAll(".list-item"); // NodeList of all matches
// Reading and writing content
btn.textContent = "Click me";
btn.innerHTML = "<strong>Click me</strong>"; // be careful with user input — XSS risk
input.value; // read text input
// Changing styles and classes
btn.classList.add("active");
btn.classList.remove("hidden");
btn.classList.toggle("open");
btn.style.color = "red";
// Creating and inserting elements
const li = document.createElement("li");
li.textContent = "New item";
list.appendChild(li);
list.insertAdjacentHTML("beforeend", "<li>Fast insert</li>");
// Removing elements
li.remove();
Events
// addEventListener is always preferred over inline onclick attributes
btn.addEventListener("click", function(event) {
event.preventDefault(); // stop form submit / link navigation
event.stopPropagation(); // stop event bubbling up the DOM tree
console.log(event.target);
});
// Event delegation — one listener for many children (efficient)
list.addEventListener("click", function(event) {
if (event.target.matches("li")) {
console.log("Clicked:", event.target.textContent);
}
});
// Common events
// click, dblclick, mouseenter, mouseleave, mousemove
// keydown, keyup, keypress
// input, change, submit, focus, blur
// DOMContentLoaded, load, resize, scroll
Phase 4 — Asynchronous JavaScript (3–4 weeks)
This is where most beginners get stuck. Take your time.
The event loop
JavaScript is single-threaded — it can only do one thing at a time. The event loop lets it handle I/O (network, timers, user input) without blocking:
- Call stack executes synchronous code top to bottom.
- When async work (fetch, setTimeout) is started, it is handed off to Web APIs.
- When the async work completes, its callback is placed in the callback queue.
- Once the call stack is empty, the event loop moves the next callback from the queue onto the stack.
Callbacks → Promises → async/await
// Callback style (old) — "callback hell" with nested callbacks
fetch("/api/user", function(user) {
fetch("/api/posts/" + user.id, function(posts) {
// deeply nested, hard to read
});
});
// Promise style — chainable, flat
fetch("/api/user")
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
// async/await — reads like synchronous code (preferred)
async function loadUserPosts() {
try {
const userRes = await fetch("/api/user");
const user = await userRes.json();
const postsRes = await fetch(`/api/posts/${user.id}`);
const posts = await postsRes.json();
return posts;
} catch (error) {
console.error("Failed to load:", error);
throw error; // re-throw so the caller can handle it
}
}
Fetching data (the Fetch API)
// GET
const response = await fetch("https://api.example.com/users");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = await response.json();
// POST with JSON body
const res = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
});
const newUser = await res.json();
// Parallel requests — faster than sequential
const [users, posts] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]);
Phase 5 — Modern JavaScript (ES6+) (2–3 weeks)
ES6 (2015) transformed the language. Learn these features thoroughly — they appear everywhere.
| Feature | Example | Why it matters |
|---|---|---|
let / const |
const x = 1 |
Block scope, prevents var bugs |
| Arrow functions | x => x * 2 |
Shorter syntax, lexical this |
| Template literals | `Hello ${name}` |
Readable string interpolation |
| Destructuring | const {a, b} = obj |
Cleaner variable extraction |
| Spread / rest | [...arr], ...args |
Immutable updates, variadic functions |
| Default params | fn(x = 0) |
Removes manual null checks |
| Classes | class Dog extends Animal |
Cleaner OOP syntax |
| Modules | import / export |
Code splitting, encapsulation |
| Optional chaining | obj?.prop?.sub |
Safe deep access |
| Nullish coalescing | val ?? fallback |
Precise fallback logic |
Promise.all |
await Promise.all([...]) |
Parallel async operations |
Array.at(-1) |
last item without .length-1 |
Readable index from end |
Object.fromEntries |
build object from entries | Pairs with Object.entries for transforms |
structuredClone |
deep clone without libraries | Built-in, reliable deep copy |
Modules
// math.js — named exports
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export const PI = 3.14159;
// utils.js — default export
export default function formatCurrency(amount, currency = "USD") {
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
}
// app.js — importing
import formatCurrency from "./utils.js";
import { add, PI } from "./math.js";
import * as math from "./math.js"; // namespace import
Phase 6 — Package ecosystem and build tools (1–2 weeks)
npm / pnpm / yarn
npm init -y # create package.json
npm install axios # install a dependency
npm install -D eslint # install a dev dependency
npm run build # run the "build" script from package.json
npm outdated # see which packages have updates
npm audit # check for security vulnerabilities
pnpm is faster and uses less disk space — worth switching to for new projects.
Vite — the modern build tool
npm create vite@latest my-app -- --template vanilla
cd my-app && npm install && npm run dev
Vite gives you:
- Hot module replacement (HMR) — instant updates without full page reload
- ES module-based dev server — no bundling during development
- Optimised production build with Rollup under the hood
ESLint + Prettier
// .eslintrc.json — enforce code quality rules
{
"extends": ["eslint:recommended"],
"env": { "browser": true, "es2022": true },
"rules": {
"no-unused-vars": "error",
"no-console": "warn"
}
}
// .prettierrc — enforce consistent formatting
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100
}
Phase 7 — A front-end framework: React (6–8 weeks)
React is the dominant front-end framework in 2025 by employer demand. Learning it after mastering vanilla JS makes the concepts click instead of feeling like magic.
Why React works the way it does
React solves UI as a function of state: whenever your data changes, React re-renders only the parts of the UI that changed. You describe what the UI should look like, React figures out how to update the DOM.
Core concepts
// Components — reusable UI building blocks
function UserCard({ name, email, avatarUrl }) {
return (
<div className="card">
<img src={avatarUrl} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// useState — local component state
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// useEffect — side effects (fetch, timers, subscriptions)
import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/users")
.then(r => r.json())
.then(data => {
setUsers(data);
setLoading(false);
});
}, []); // empty array = run once on mount
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Essential hooks
| Hook | Purpose |
|---|---|
useState |
Local state in a component |
useEffect |
Side effects (fetch, DOM, timers) |
useRef |
Mutable value that does not trigger re-render; direct DOM access |
useContext |
Read from a React Context (global state) |
useMemo |
Memoize expensive computed values |
useCallback |
Memoize functions passed as props |
useReducer |
Complex state logic with reducer pattern |
useId |
Generate unique IDs for accessibility |
State management options
| Solution | Best for |
|---|---|
useState + prop drilling |
Simple components, 1–2 levels deep |
useContext |
Theme, auth, locale — rarely changing global state |
| Zustand | Medium apps, minimal boilerplate |
| TanStack Query | Server state — fetching, caching, refetching |
| Redux Toolkit | Large apps with complex shared state |
Next.js — the React framework
For production apps, use Next.js instead of plain React:
npx create-next-app@latest my-app
Next.js adds:
- File-system routing (
app/directory) - Server components and server actions
- API routes
- Optimised images, fonts, and scripts
- Static generation and server-side rendering
Phase 8 — Back-end with Node.js (4–6 weeks)
Node.js lets you run JavaScript on the server using the same language you already know.
Express.js — minimal REST API
import express from "express";
import cors from "cors";
const app = express();
app.use(express.json());
app.use(cors());
// In-memory data store (replace with DB)
let users = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" }
];
// GET all
app.get("/api/users", (req, res) => {
res.json({ success: true, data: users });
});
// GET by ID
app.get("/api/users/:id", (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ success: false, error: "Not found" });
res.json({ success: true, data: user });
});
// POST create
app.post("/api/users", (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ success: false, error: "name and email required" });
}
const newUser = { id: Date.now(), name, email };
users.push(newUser);
res.status(201).json({ success: true, data: newUser });
});
// PUT update
app.put("/api/users/:id", (req, res) => {
const index = users.findIndex(u => u.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ success: false, error: "Not found" });
users[index] = { ...users[index], ...req.body };
res.json({ success: true, data: users[index] });
});
// DELETE
app.delete("/api/users/:id", (req, res) => {
const before = users.length;
users = users.filter(u => u.id !== Number(req.params.id));
if (users.length === before) {
return res.status(404).json({ success: false, error: "Not found" });
}
res.json({ success: true, message: "Deleted" });
});
app.listen(3000, () => console.log("API running on http://localhost:3000"));
Middleware pattern
// Authentication middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "No token" });
try {
req.user = verifyToken(token); // your JWT verification
next(); // pass control to the next handler
} catch {
res.status(401).json({ error: "Invalid token" });
}
}
// Apply to all routes under /api/protected
app.use("/api/protected", requireAuth);
// Error handling middleware — must have 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: "Internal server error" });
});
Node.js ecosystem alternatives
| Framework | Philosophy | Best for |
|---|---|---|
| Express | Minimal, unopinionated | APIs, learning |
| Fastify | Fast, schema-based | High-throughput APIs |
| NestJS | Structured, Angular-inspired | Large enterprise apps |
| Hono | Edge-first, tiny | Cloudflare Workers, Bun |
Phase 9 — Databases (3–4 weeks)
PostgreSQL with Prisma (recommended stack)
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// Create
const user = await prisma.user.create({
data: { name: "Alice", email: "alice@example.com" }
});
// Read with relations
const userWithPosts = await prisma.user.findUnique({
where: { email: "alice@example.com" },
include: { posts: true }
});
// Update
await prisma.user.update({
where: { id: 1 },
data: { name: "Alice Smith" }
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
// Paginated query
const users = await prisma.user.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: "desc" }
});
SQL vs NoSQL for JavaScript developers
| PostgreSQL / MySQL | MongoDB | |
|---|---|---|
| Data shape | Structured, schema-required | Flexible, schema-optional |
| Relations | JOINs, foreign keys | Embedding or references |
| JS library | Prisma, Drizzle, Knex | Mongoose, native driver |
| Best for | Relational data, financial apps | Flexible schemas, rapid prototyping |
Phase 10 — Testing (2–3 weeks)
Testing pyramid
| Layer | Tool | What to test |
|---|---|---|
| Unit | Vitest / Jest | Pure functions, utilities, hooks |
| Integration | Vitest + Supertest | API endpoints, DB queries |
| E2E | Playwright | Critical user journeys |
Unit tests with Vitest
// math.test.js
import { describe, it, expect } from "vitest";
import { add, subtract } from "./math.js";
describe("add", () => {
it("adds two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("handles negative numbers", () => {
expect(add(-1, 1)).toBe(0);
});
});
describe("subtract", () => {
it("subtracts correctly", () => {
expect(subtract(5, 3)).toBe(2);
});
});
Integration tests with Supertest
import request from "supertest";
import { app } from "./app.js";
describe("GET /api/users", () => {
it("returns 200 and an array", async () => {
const res = await request(app).get("/api/users");
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
expect(Array.isArray(res.body.data)).toBe(true);
});
});
describe("POST /api/users", () => {
it("creates a user and returns 201", async () => {
const res = await request(app)
.post("/api/users")
.send({ name: "Test User", email: "test@example.com" });
expect(res.status).toBe(201);
expect(res.body.data.name).toBe("Test User");
});
it("returns 400 when email is missing", async () => {
const res = await request(app)
.post("/api/users")
.send({ name: "No Email" });
expect(res.status).toBe(400);
});
});
Phase 11 — Deployment and DevOps basics (2–3 weeks)
Dockerfile for a Node.js app
# Multi-stage build — keeps production image small
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
Deployment platform options
| Platform | Free tier | Best for |
|---|---|---|
| Vercel | Generous | Next.js, front-end |
| Netlify | Generous | Static sites, serverless |
| Railway | $5 credit | Full-stack + databases |
| Render | 750 hrs/mo | APIs, background workers |
| Fly.io | 3 shared VMs | Global edge deployment |
| Cloudflare Workers | 100k req/day | Edge functions, Hono |
GitHub Actions CI/CD
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Full JavaScript technology map
CORE LANGUAGE
├── Fundamentals → types, functions, scope, closures, prototypes
├── DOM API → querySelector, events, fetch
├── ES6+ → modules, destructuring, async/await, classes
└── Browser APIs → localStorage, ServiceWorker, WebSockets, WebRTC
FRONT-END
├── Vanilla → DOM manipulation, event delegation
├── React → hooks, context, React Router
│ ├── Next.js → SSR, SSG, App Router, Server Actions
│ └── Remix → web standards, nested routes
├── Vue.js → Options API → Composition API
│ └── Nuxt.js → meta-framework for Vue
└── Svelte → compiled, minimal runtime
BACK-END (Node.js)
├── Express → minimal, widely used
├── Fastify → fast, schema-first
├── NestJS → structured, TypeScript-first
└── Hono → edge-first, multi-runtime
RUNTIMES
├── Node.js → V8 + libuv, npm ecosystem
├── Deno → secure-by-default, TypeScript native
└── Bun → fastest JS runtime, all-in-one toolkit
DATABASES
├── SQL → PostgreSQL + Prisma or Drizzle
├── NoSQL → MongoDB + Mongoose
└── Edge → SQLite (Turso), D1 (Cloudflare)
TESTING
├── Unit → Vitest, Jest
├── Integration → Supertest
└── E2E → Playwright, Cypress
TOOLING
├── Build → Vite, esbuild, Rollup
├── Lint/Format → ESLint, Prettier
├── Types → TypeScript (learn after JS basics)
└── Monorepo → Turborepo, Nx
Realistic 12-month timeline
| Month | Focus | Milestone |
|---|---|---|
| 1 | Web basics, VS Code, JS fundamentals (types, functions) | Console calculator |
| 2 | Arrays, objects, closures, DOM basics | Interactive to-do list |
| 3 | Events, forms, DOM manipulation | Form validation project |
| 4 | Async, Promises, fetch, error handling | Weather app with a real API |
| 5 | ES6+ modules, Vite, npm, ESLint | Modular project with build step |
| 6 | React basics (JSX, useState, useEffect) | Product listing with filters |
| 7 | React Router, forms, custom hooks | Multi-page React app |
| 8 | TanStack Query or SWR, React + API | Full CRUD front-end |
| 9 | Node.js, Express, REST API | Back-end for your front-end |
| 10 | PostgreSQL + Prisma, auth (JWT) | Full-stack app with login |
| 11 | Testing (Vitest, Supertest, Playwright) | CI pipeline passing |
| 12 | Deployment, Docker basics, portfolio polish | 3 projects live in production |
Portfolio project ideas
| Project | Skills demonstrated | Difficulty |
|---|---|---|
| Real-time chat app | WebSockets, auth, Node.js | Medium |
| Expense tracker | CRUD, charts, CSV export | Easy–Medium |
| Job board | Full-stack, search, pagination | Medium |
| E-commerce store | Cart, payments (Stripe), auth | Hard |
| Markdown blog | Next.js, MDX, SSG, RSS | Medium |
| URL shortener | Node.js, Redis, analytics | Medium |
| Recipe finder | External API, filtering, favourites | Easy–Medium |
JavaScript developer roles and salaries (2025)
| Role | Primary skills | US median salary |
|---|---|---|
| Junior JS Developer | Vanilla JS, React basics | $55,000–$75,000 |
| Front-end Developer | React, CSS, Next.js | $80,000–$110,000 |
| Back-end Developer (Node) | Node.js, Express, SQL | $85,000–$115,000 |
| Full-Stack Developer | React + Node.js | $90,000–$130,000 |
| Senior Full-Stack | Architecture, performance, mentorship | $120,000–$160,000 |
| Staff / Principal Engineer | System design, org-wide impact | $160,000–$220,000+ |
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
Using == instead of === |
Unexpected type coercion bugs | Always use === |
| Mutating state directly in React | Component does not re-render | Use setState or spread to clone |
| Forgetting to handle Promise rejections | Silent failures | Always catch or use try/catch |
| Skipping TypeScript | Runtime type errors at scale | Add TypeScript after JS basics |
var instead of let/const |
Hoisting and function scope bugs | Never use var |
| Not understanding the event loop | Confusing async behaviour | Study the call stack + task queue |
| Learning frameworks before JS | Framework magic is confusing | Spend 3–4 months on vanilla JS first |
| Building nothing while learning | Zero portfolio, zero experience | Build a project at each phase |
JavaScript vs alternatives
| JavaScript | TypeScript | Python | Go | |
|---|---|---|---|---|
| Type safety | None (dynamic) | Strong (static) | Optional (hints) | Strong (compiled) |
| Browser support | Native | Compiles to JS | No | No |
| Back-end | Node.js (excellent) | Node.js (excellent) | Excellent | Excellent |
| Ecosystem | Largest (npm) | Shares npm | PyPI (large) | Go modules |
| Learning curve | Medium | Medium+TS overhead | Gentle | Moderate |
| Job market | Largest | Very large | Very large | Large |
| Best for | Web, full-stack | Large-scale JS projects | Data science, scripting | Systems, CLIs |
Recommendation: Learn JavaScript first. Add TypeScript when you are comfortable — it is the same language with types layered on top, and the combination is the most employable skill set in web development today.
FAQ
Do I need to learn HTML and CSS before JavaScript? Yes — spend 2–4 weeks on HTML and CSS basics first. JavaScript without HTML/CSS is like a painter without a canvas. You do not need to master CSS before touching JS, but you need the fundamentals.
Should I learn TypeScript or JavaScript first? JavaScript first, always. TypeScript is a superset of JavaScript — once you understand JavaScript's quirks, TypeScript makes sense because you understand what it is protecting you from. Jumping straight into TypeScript before understanding JavaScript leads to cargo-culting types without understanding the underlying behaviour.
Which framework should I learn — React, Vue, or Angular? Learn React first. It has the largest ecosystem, the highest employer demand, and is the most transferable foundation (understanding React makes Vue and Angular much easier to learn later). Vue is friendlier for small projects. Angular is dominant in enterprise environments.
How long does it take to get a junior developer job? With consistent daily practice (2–4 hours), most people reach junior-employable level in 9–14 months. If you already have a software background, 4–6 months is realistic. You do not need to complete the entire roadmap — employers hire juniors who know JS fundamentals, one framework, and have 2–3 portfolio projects.
Should I learn Node.js or stick to front-end? Start with front-end. Once you are comfortable with React, add Node.js. Full-stack developers command higher salaries and have broader job options. JavaScript's ability to run on both client and server is its biggest competitive advantage — use it.
Is JavaScript still relevant in 2025? More than ever. JavaScript is the most-used programming language on GitHub for the 11th consecutive year. The npm ecosystem has over 2 million packages. WebAssembly, AI tooling (Vercel AI SDK, LangChain.js), and edge computing have all embraced JavaScript/TypeScript as a first-class language. The language evolves quickly but maintains backward compatibility — code written in 2015 still runs today.