| Hook | Purpose | Example |
|---|---|---|
| useState | Manage local state | const [count, setCount] = useState(0); |
| useEffect | Side effects (API calls, subs) | useEffect(()=>{ fetchData(); }, [dep]); |
| useContext | Access context values | const theme = useContext(ThemeContext); |
| useReducer | Complex state logic | useReducer(reducer, initialState); |
| useRef | Persist values / DOM refs | const inputRef = useRef(); |
| useMemo | Memoize expensive computations | useMemo(()=>calc(items), [items]); |
| useCallback | Memoize functions | useCallback(()=>doSomething(), []); |
- Store → Centralized state container
- Reducer → Pure function
(state, action) => newState - Action → Plain object
{ type, payload } - Dispatch → Sends actions to reducers
- Selector → Extracts data from store
- Middleware → Intercepts actions (e.g., Thunk, Saga)
- Provider → Makes store available to React components
RTK simplifies Redux by removing boilerplate. It uses createSlice to combine actions and reducers.
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
// RTK uses Immer, so you can "mutate" state safely
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});import { createStore } from 'redux';
// Reducer
const counterReducer = (state = { count: 0 }, action) => {
switch(action.type) {
case 'INCREMENT': return { count: state.count + 1 };
case 'DECREMENT': return { count: state.count - 1 };
default: return state;
}
};
// Store
const store = createStore(counterReducer);
// Dispatch
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1 }import { Provider, useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>{count}</p>
<button onClick={()=>dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={()=>dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
}- Destructuring →
const { name, age } = person; - Spread Operator → Copy + modify arrays/objects
- Optional Chaining →
user?.profile?.email - Nullish Coalescing →
const val = input ?? "default"; - Async/Await →
const data = await fetchData(); - Template Literals →
`Hello, ${name}!` - Strict vs Loose Equality →
===vs== - Truthy/Falsy →
false, 0, "", null, undefined, NaNvs everything else
The way you declare variables affects their scope, mutability, and how they behave during the execution phase.
| Feature | var |
let |
const |
|---|---|---|---|
| Scope | Function Scope | Block Scope { } |
Block Scope { } |
| Hoisting | Hoisted (initialized as undefined) |
Hoisted (Temporal Dead Zone) | Hoisted (Temporal Dead Zone) |
| Reassignable | ✅ Yes | ✅ Yes | ❌ No |
| Redeclarable | ✅ Yes | ❌ No | ❌ No |
// Block Scope Example
if (true) {
var functionScoped = "I am visible outside!";
let blockScoped = "I am hidden outside!";
}
console.log(functionScoped); // "I am visible outside!"
console.log(blockScoped); // ReferenceError
// Const Mutability (Objects/Arrays)
const user = { name: "Alice" };
user.name = "Bob"; // ✅ Allowed (mutating property)
user = {}; // ❌ TypeError (reassigning variable)Event-driven architecture: Node.js is built around an event loop that listens for events (like incoming requests, timers, or file reads) and dispatches callbacks when they’re ready.
Single-threaded execution: Your JavaScript runs on one main thread, but Node.js can still handle thousands of concurrent connections thanks to non-blocking I/O.
libuv: This C library powers Node’s event loop and provides a thread pool for heavy tasks (file system, crypto, DNS). So while JS is single-threaded, Node can offload work to background threads.
const express = require('express');
const app = express();
app.use(express.json());
app.get('/items', (req,res)=> res.send("GET items"));
app.post('/items', (req,res)=> res.send("POST item"));
app.put('/items/:id', (req,res)=> res.send("PUT item " + req.params.id));
app.listen(3000, ()=> console.log("Server running"));Model.find({});→ Find allModel.findOne({ name: "John" });→ Find oneModel.findById(id);→ Find by IDnew Model({ field: val }).save();→ InsertModel.updateOne({ _id:id }, { $set:{ field:val } });→ UpdateModel.deleteOne({ _id:id });→ Delete
- Projection →
Model.find({}, 'name age'); - Sorting →
Model.find().sort({ age: -1 }); - Pagination →
Model.find().skip(10).limit(5); - Population (relations) →
Model.find().populate('author'); - Aggregation
Model.aggregate([ { $match: { status: "active" } }, { $group: { _id: "$category", total: { $sum: 1 } } } ]);
- Schema Validation
const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, age: { type: Number, min: 18 } });
- Indexes →
userSchema.index({ email: 1 }); - Middleware (Hooks) →
userSchema.pre('save', function(next){ ... }); - Virtuals →
userSchema.virtual('fullName').get(function(){ return this.firstName + " " + this.lastName; });
- Installed with
npm install <package> - Required in production runtime.
- Examples:
"express": "^4.18.2""mongoose": "^7.0.0"
- Installed with
npm install <package> --save-dev - Needed only in development/testing/build.
- Examples:
"nodemon": "^3.0.1"(auto-restart server)"jest": "^29.0.0"(testing framework)"eslint": "^8.0.0"(linting)
- scripts → Custom commands
- peerDependencies → Packages expected to be installed by the consumer (e.g., React libraries).
- optionalDependencies → Not required; app runs even if missing.
- bundledDependencies → Included when publishing a package.
- engines → Specify Node.js version compatibility.
- config → Custom configuration values for scripts.
| Aspect | dependencies | devDependencies |
|---|---|---|
| Purpose | Runtime use | Development only |
| Installed in Prod | ✅ Yes | ❌ No |
| Examples | express, mongoose | nodemon, jest, eslint |
| Command | npm install |
npm install --save-dev |
####🔹 Testing with Jest & React Testing Library
import counterReducer, { increment } from './counterSlice';
test('should handle initial state', () => {
expect(counterReducer(undefined, { type: 'unknown' })).toEqual({ value: 0 });
});
test('should handle increment', () => {
const previousState = { value: 10 };
expect(counterReducer(previousState, increment())).toEqual({ value: 11 });
});import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on button click', () => {
render(<Counter />);
const button = screen.getByText('+');
fireEvent.click(button);
expect(screen.getByText('1')).toBeInTheDocument();
});Jest is the industry standard for testing JavaScript. Understanding its lifecycle and matchers is key to writing reliable unit tests.
Used to set up or tear down environments before/after tests (e.g., clearing a database or resetting a mock).
| Function | Description |
|---|---|
beforeAll() |
Runs once before all tests in a file/describe block |
beforeEach() |
Runs before every single test |
afterEach() |
Runs after every single test |
afterAll() |
Runs once after all tests are finished |
Matchers allow you to validate values in different ways.
test('basic matchers', () => {
expect(2 + 2).toBe(4); // Exact equality (Object.is)
expect({a: 1}).toEqual({a: 1}); // Deep equality (objects/arrays)
expect(n).toBeNull(); // Only matches null
expect(n).toBeDefined(); // Opposite of undefined
expect(n).toBeTruthy(); // Matches anything an if-statement treats as true
});- Redux vs Context API? → Redux is more scalable with middleware and dev tools; Context is simpler but not ideal for complex state.
- Difference between
findOneandfindByIdin Mongoose? →findOnematches any field;findByIdspecifically matches_id. - When to use Aggregation in Mongoose? → For analytics, grouping, and complex queries.
- What does
populatedo? → Replaces referenced ObjectId with actual document data. - Why use devDependencies? → To keep production lean; dev tools aren’t needed in runtime.
- What are peerDependencies? → Packages expected to be installed by the consumer (e.g., React version for a library).
- Difference between
useEffectanduseLayoutEffect? →useEffectruns after paint;useLayoutEffectruns before paint (blocking). - Why use middleware in Redux? → To handle async logic and side effects.
- Difference between
npm installandnpm install --save-dev? → First adds to dependencies, second adds to devDependencies. - What is the purpose of indexes in Mongoose? → Speed up queries by optimizing lookups.
- Redux: Store → Reducer → Action → Dispatch → Selector → Middleware → UI.
- Mongoose: Populate for relationships, Aggregate for analytics, Indexes for performance, Middleware for lifecycle hooks.
- Package.json: Dependencies for runtime, devDependencies for dev tools, peerDependencies for shared libs.
- Run multiple instances across servers/containers
- Use load balancers (NGINX, HAProxy, AWS ELB, Kubernetes)
- Distributes traffic, avoids single-node bottlenecks
- Pattern: Use Node.js
clustermodule to spawn workers across CPU cores - Example:
const cluster = require('cluster'); const http = require('http'); const os = require('os'); if (cluster.isMaster) { os.cpus().forEach(() => cluster.fork()); } else { http.createServer((req, res) => { res.end("Hello from worker " + process.pid); }).listen(3000); }
- Benefit: Utilizes all cores on a single machine.
- Pattern: Break a monolithic app into smaller services (auth, payments, orders).
- Tools: Docker, Kubernetes, message brokers (RabbitMQ, Kafka).
- Benefit: Independent scaling, easier maintenance, fault isolation.
- Pattern: Use queues to decouple producers and consumers.
- Tools: RabbitMQ, Kafka, Redis Pub/Sub.
- Benefit: Smooths out spikes in traffic, improves resilience.
- Pattern: Store frequently accessed data in memory.
- Tools: Redis, Memcached.
- Benefit: Reduces database load, speeds up responses.
- Pattern: Use replication, sharding, or read/write splitting.
- Tools: MongoDB sharding, PostgreSQL replication.
- Benefit: Handles large datasets and high query volumes.
- Pattern: Central entry point for routing, authentication, rate limiting.
- Tools: Kong, NGINX, AWS API Gateway.
- Benefit: Simplifies client interaction, adds security and monitoring.
- High traffic, simple app → Clustering + caching.
- Complex domain, multiple teams → Microservices + API Gateway.
- Spiky workloads → Message queues + autoscaling.
- Data-heavy apps → Database sharding + caching.
- Use retries with exponential backoff
- Implement circuit breakers (e.g., with libraries like
opossum) - Gracefully degrade when dependencies fail
- Protect APIs from abuse and overload
- Tools: NGINX, API Gateway, Redis-based counters
- Strategies: fixed window, sliding window, token bucket
- Cache frequent responses (Redis, CDN)
- Ensure idempotent endpoints (e.g., POST with unique request IDs)
- Prevent duplicate processing during retries
- Set timeouts for external calls
- Provide fallback responses or cached data
- Avoid hanging requests that block resources
- Collect metrics (latency, error rates, throughput)
- Use distributed tracing (Jaeger, Zipkin, OpenTelemetry)
- Log structured events for debugging
- Validate inputs to prevent injection attacks
- Use authentication & authorization (OAuth2, JWT)
- Apply HTTPS everywhere
- Support multiple API versions (v1, v2)
- Use semantic versioning for breaking changes
- Provide backward compatibility when possible
- Return partial data if full response unavailable
- Inform clients of reduced functionality
- Keep core features available under stress
- Bulkhead: isolate resources to prevent cascading failures
- Circuit Breaker: stop calls to failing services temporarily
- Retry with Backoff: retry failed requests intelligently
- Fail Fast: quickly reject requests when system is overloaded
- ✅ Retries with backoff
- ✅ Circuit breakers
- ✅ Rate limiting
- ✅ Idempotency
- ✅ Timeouts & fallbacks
- ✅ Monitoring & tracing
- ✅ Secure endpoints
- ✅ Graceful degradation