Most real JavaScript applications use four programming styles at once: object-oriented code to model things, functional code to transform data, asynchronous code to wait for networks and disks, and event-driven code to react to users and messages. Knowing which one fits which job makes code easier to read and debug. This guide explains all four with short, current JavaScript examples for Node.js and the browser.
OOP groups data and behaviour into classes (use #private fields and prefer composition to deep inheritance). Functional programming uses pure functions and immutable data (map, filter, reduce, spread). Asynchronous programming uses promises and async/await so slow work doesn't block. Event-driven programming reacts to events through listeners and the event loop. Good applications combine all four.
1. Why paradigms matter
A paradigm is a way of organising code. None is "best"; each solves a different problem:
| Paradigm | Organises code around | Typical use |
|---|---|---|
| Object-oriented | Objects that hold state and behaviour | Domain models, UI components, services |
| Functional | Pure functions and immutable data | Data transformation, validation, reducers |
| Asynchronous | Work that finishes later | Network calls, database queries, file I/O |
| Event-driven | Things that happen | Clicks, messages, webhooks, streams |
JavaScript supports all four natively, which is why it helps to understand each one separately before mixing them.
2. Object-oriented programming
A class is a blueprint; an object is an instance with its own data. Modern JavaScript supports truly private fields with #:
class Employee {
#salary;
constructor(name, salary) {
this.name = name;
this.#salary = salary;
}
get monthlyPay() {
return Math.round(this.#salary / 12);
}
work() {
return `${this.name} does general work.`;
}
}
class Manager extends Employee {
constructor(name, salary, department) {
super(name, salary);
this.department = department;
}
work() {
return `${this.name} manages ${this.department}.`;
}
}
const staff = [new Employee('Asha', 600000), new Manager('Ravi', 1200000, 'Sales')];
staff.forEach((p) => console.log(p.work()));The four classic principles, in plain terms:
#salary can't be read or changed from outside.Manager extends Employee.work() differs, and the loop doesn't care which is which.monthlyPay) without knowing the details behind it.Long inheritance chains become fragile: a change in a parent class ripples through every child. Often it's clearer for an object to have helpers (a Mailer, a PriceCalculator) than to be a subclass of something. Use extends for genuine "is a" relationships only.
3. Functional programming
Functional code avoids changing shared data. Functions take input and return new output:
// Pure: same input, same output, no side effects
const withGst = (amount) => Math.round(amount * 1.18);
// Immutability: copy and change, don't mutate
const order = { id: 7, status: 'pending' };
const paid = { ...order, status: 'paid' }; // order is unchanged
// Higher-order functions
const orders = [
{ id: 1, total: 1200, paid: true },
{ id: 2, total: 450, paid: false },
{ id: 3, total: 3000, paid: true },
];
const paidTotal = orders
.filter((o) => o.paid)
.map((o) => o.total)
.reduce((sum, t) => sum + t, 0); // 4200Newer array methods return copies instead of changing the original: toSorted(), toReversed(), toSpliced() and with(). Use them instead of sort() and reverse(), which change the array in place. structuredClone() makes a deep copy of plain data.
4. Asynchronous programming
JavaScript runs your code on one thread. Slow operations, such as an HTTP request or a database query, run in the background, and your code continues when they finish. There are three generations of syntax.
Callbacks came first. They nest badly when one step depends on another ("callback hell"), so use them only where an API requires them.
Promises represent a value that will arrive later:
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
wait(500).then(() => console.log('half a second later'));async/await is the modern way to write promise-based code so it reads top to bottom:
async function getUser(id) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000); // 5-second timeout
try {
const res = await fetch(`https://api.example.com/users/${id}`, {
signal: controller.signal,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} finally {
clearTimeout(timer);
}
}fetch is built into browsers and into current Node.js, so you don't need a library for simple HTTP calls. Note that fetch only rejects on network errors, not on HTTP errors, which is why the code checks res.ok.
Run independent tasks in parallel instead of one after another:
const [user, orders] = await Promise.all([getUser(1), getOrders(1)]);
// Keep going even if some fail
const results = await Promise.allSettled(ids.map(getUser));Every await should sit inside a try/catch somewhere up the call chain, and every .then() chain needs a .catch(). In current Node.js versions an unhandled promise rejection stops the process by default.
5. Event-driven programming
In event-driven code, you register listeners and the program runs them when something happens. In the browser:
document.querySelector('#buy').addEventListener('click', (event) => {
event.preventDefault();
console.log('Buy button clicked');
});In Node.js, many core objects (servers, streams, sockets) are event emitters, and you can create your own:
import { EventEmitter } from 'node:events';
const shop = new EventEmitter();
shop.on('order:paid', (order) => console.log(`Send receipt for #${order.id}`));
shop.on('order:paid', (order) => console.log(`Start delivery for #${order.id}`));
shop.emit('order:paid', { id: 42 });The code that emits order:paid doesn't need to know who listens. That loose coupling is the main benefit: you can add a new reaction, such as an SMS, without touching the payment code.
How the event loop runs it all
The event loop is what makes async and event-driven code work together. It runs your synchronous code first, then microtasks (promise callbacks), then the next task from the queue (timers, I/O, events), and repeats:
console.log('1 sync');
setTimeout(() => console.log('4 timer task'), 0);
Promise.resolve().then(() => console.log('3 microtask'));
console.log('2 sync');
// Prints 1, 2, 3, 4Long synchronous work, such as a big loop, blocks the loop, and nothing else runs until it finishes: no clicks, no requests. Move heavy CPU work to a Worker (browser) or worker_threads (Node.js).
6. Combining the four
A typical order flow uses all of them:
- An
Orderclass holds the order's data and rules (OOP). - Pure functions calculate totals, GST and discounts (functional).
- The payment API is called with async/await (asynchronous).
- On success, an
order:paidevent triggers the receipt, the stock update and the delivery request (event-driven).
7. Running JavaScript on Domain India
Use a current long-term-support (LTS) release of Node.js, such as Node.js 24, and upgrade before your version reaches end of life.
- Shared cPanel hosting: the Setup Node.js App tool offers Node.js 20, 22 and 24 on our cPanel servers. Choose 22 or 24, because Node.js 20 is past its end of life. See deploy a Node.js app on shared hosting.
- App Platform: Node.js apps are detected and built automatically; anything else needs a Dockerfile. The App Platform doesn't support WebSockets, so for real-time features such as live chat, use a VPS. Start with App Platform getting started.
- VPS: full root control for any Node.js setup, including WebSockets and background workers. VPS plans are self-managed.
- 512 MB RAM per app
- 1 vCPU
- 5 GB NVMe SSD
- PostgreSQL Database
The price on the card is a live Domain India list price and excludes 18% GST. For the same ideas in PHP, see asynchronous programming in PHP and event-driven programming in PHP.
What are the main programming paradigms in JavaScript?
JavaScript supports object-oriented programming with classes, functional programming with pure functions and array methods, asynchronous programming with promises and async/await, and event-driven programming with event listeners and emitters. Most applications use all four.
What is the difference between asynchronous and event-driven programming?
Asynchronous programming is about work that finishes later, such as a network request, and continuing when it does. Event-driven programming is about reacting to things that happen, such as a click or a message. Both rely on JavaScript's event loop.
Should I use callbacks, promises or async/await?
Use async/await for new code. It is built on promises and reads top to bottom. Use Promise.all or Promise.allSettled to run independent tasks in parallel, and callbacks only where an older API requires them.
How do I make a field private in a JavaScript class?
Prefix it with #, for example #salary. Private fields can only be read or changed inside the class body.
Why does my Node.js process stop on a rejected promise?
Current Node.js versions end the process on an unhandled promise rejection by default. Wrap await calls in try/catch and add .catch() to promise chains.
Can I run Node.js apps on Domain India shared hosting?
Yes, on cPanel hosting through Setup Node.js App, which offers Node.js 20, 22 and 24. For apps that need WebSockets or background workers, use a VPS.
Ready to build? Try your app on the App Platform, host it with cPanel hosting, or take full control with a VPS.
Node.js apps are detected and built automatically, with free SSL and your own domain.
See the App Platform