Full stack interviews cover the entire web development spectrum — from pixel-level CSS and React state management to database indexing and cloud deployments. This guide covers 50 questions interviewers actually ask, with concise answers and code examples.
Quick reference
| Topic | What gets tested |
|---|---|
| JavaScript / TypeScript | Closures, async/await, types, ES6+ |
| React | Hooks, state, rendering, performance |
| HTML / CSS | Semantics, Flexbox/Grid, responsive |
| Node.js / Express | REST APIs, middleware, async patterns |
| Databases | SQL vs NoSQL, indexing, transactions |
| APIs | REST design, GraphQL basics, versioning |
| Auth & Security | JWT, OAuth, XSS, CSRF, SQL injection |
| System design | Caching, scaling, microservices |
| DevOps basics | Docker, CI/CD, environment config |
| Behavioral | Ownership, trade-offs, delivery |
JavaScript / TypeScript
1. What is a closure and why is it useful?
A closure is a function that retains access to its enclosing scope even after the outer function has returned.
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count,
};
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.get(); // 2
Why useful: encapsulation of private state, factory functions, memoization, module pattern.
2. Explain the JavaScript event loop.
JavaScript is single-threaded. The event loop coordinates three queues:
| Component | Contains | Priority |
|---|---|---|
| Call stack | Currently executing functions | Executes first |
| Microtask queue | Promise.then, queueMicrotask |
Drains after each task |
| Macrotask queue | setTimeout, setInterval, I/O |
One task per loop tick |
console.log('1');
setTimeout(() => console.log('3'), 0);
Promise.resolve().then(() => console.log('2'));
// Output: 1 → 2 → 3
3. What is the difference between == and ===?
| Operator | Name | Type coercion |
|---|---|---|
== |
Loose equality | Yes — converts types before comparing |
=== |
Strict equality | No — types must match |
0 == '0' // true (string coerced to number)
0 === '0' // false (different types)
null == undefined // true
null === undefined // false
Rule of thumb: always use === in production code.
4. How does async/await work under the hood?
async/await is syntactic sugar over Promises.
// These are equivalent:
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(res => res.json());
}
An async function always returns a Promise. await pauses execution of that function only (not the event loop) until the Promise settles.
Error handling:
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error('Fetch failed:', err.message);
throw err; // re-throw so callers can handle
}
}
5. What are the key TypeScript benefits for full-stack development?
| Benefit | How it helps |
|---|---|
| Static typing | Catch type mismatches at compile time |
| IntelliSense | Autocomplete on shared types across front/back end |
| Shared interfaces | Same User type in React component and Express handler |
| Safer refactoring | Rename propagates everywhere |
| Generics | Reusable typed utilities without any |
// Shared type — used in both client and server
interface User {
id: number;
email: string;
role: 'admin' | 'user';
}
// API response wrapper
type ApiResponse<T> = { data: T; error: string | null };
6. What is the difference between var, let, and const?
var |
let |
const |
|
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Hoisted + initialized to undefined |
Hoisted, not initialized (TDZ) | Hoisted, not initialized (TDZ) |
| Re-assignable | Yes | Yes | No (binding) |
| Re-declarable | Yes | No | No |
TDZ = Temporal Dead Zone. Accessing let/const before declaration throws a ReferenceError.
7. Explain Promise.all, Promise.allSettled, and Promise.race.
const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/comments');
// Resolves when ALL succeed, rejects on first failure
const [users, posts] = await Promise.all([p1, p2]);
// Resolves when ALL settle (fulfilled or rejected)
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
// Resolves/rejects with whichever settles first
const fastest = await Promise.race([p1, p2, p3]);
React
8. What is the difference between useState and useReducer?
useState |
useReducer |
|
|---|---|---|
| Best for | Simple, independent values | Complex state with many sub-values or transitions |
| Update model | Setter function | Dispatch action → reducer |
| Related state | Hard to co-locate | State grouped in one reducer |
| Testing | Test component | Test pure reducer function |
// useReducer for a shopping cart
function cartReducer(state, action) {
switch (action.type) {
case 'ADD': return { ...state, items: [...state.items, action.item] };
case 'REMOVE': return { ...state, items: state.items.filter(i => i.id !== action.id) };
case 'CLEAR': return { items: [], total: 0 };
default: return state;
}
}
const [cart, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
9. When should you use useMemo and useCallback?
Both prevent unnecessary recalculation/recreation on re-renders.
| Hook | Memoizes | Use when |
|---|---|---|
useMemo |
Return value of a function | Expensive computation (filter, sort large arrays) |
useCallback |
Function reference itself | Passing callbacks to memoized child components |
// useMemo: only re-filters when products or query change
const filtered = useMemo(
() => products.filter(p => p.name.includes(query)),
[products, query]
);
// useCallback: stable reference prevents child re-render
const handleDelete = useCallback(
(id) => dispatch({ type: 'DELETE', id }),
[dispatch]
);
Caution: memoization has overhead. Only use when you can measure a real performance problem.
10. How does React reconciliation work?
React's reconciliation algorithm (Fiber) diffs the current VDOM tree against the new one and computes the minimal set of DOM updates.
Key rules:
- Element type changes → unmount old, mount new subtree
- Same type → update props in place
- Lists need keys → keys tell React which item moved vs was added/removed
// Without key: React re-renders all items on reorder
{items.map(item => <Item item={item} />)}
// With stable key: React moves only the changed DOM nodes
{items.map(item => <Item key={item.id} item={item} />)}
11. What is the Context API and when should you avoid it?
Context provides a way to pass data through the component tree without prop drilling.
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
When to avoid Context for state management:
- High-frequency updates (every context change re-renders all consumers)
- Complex state logic (use Zustand, Redux, or Jotai instead)
Context is ideal for: auth user, theme, locale, feature flags.
12. How do you prevent unnecessary re-renders in React?
| Technique | How |
|---|---|
React.memo |
Wraps component, skips re-render if props didn't change |
useMemo |
Memoizes expensive computed values |
useCallback |
Memoizes function references passed as props |
| State colocation | Move state down to the component that needs it |
| Splitting context | Separate contexts for different update rates |
const HeavyList = React.memo(function HeavyList({ items, onDelete }) {
return items.map(item => (
<Item key={item.id} item={item} onDelete={onDelete} />
));
});
HTML / CSS
13. What is the CSS box model?
Every HTML element is a rectangular box composed of:
+---------------------------+
| margin |
| +---------------------+ |
| | border | |
| | +--------------+ | |
| | | padding | | |
| | | +--------+ | | |
| | | |content | | | |
| | | +--------+ | | |
| | +--------------+ | |
| +---------------------+ |
+---------------------------+
box-sizing: content-box (default) — width/height apply to content only.box-sizing: border-box — width/height include padding + border (recommended, easier math).
14. When do you use Flexbox vs CSS Grid?
| Flexbox | CSS Grid | |
|---|---|---|
| Axis | One dimension (row OR column) | Two dimensions (rows AND columns) |
| Use case | Component internals, nav bars, card rows | Page layouts, complex grids |
| Alignment | Excellent cross-axis alignment | Full row/column control |
/* Flexbox: horizontal nav */
nav { display: flex; gap: 1rem; align-items: center; }
/* Grid: 3-column page layout */
.layout {
display: grid;
grid-template-columns: 200px 1fr 300px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
15. How do you make a layout responsive?
Core techniques:
/* 1. Mobile-first media queries */
.container { padding: 1rem; }
@media (min-width: 768px) { .container { padding: 2rem; } }
/* 2. Fluid Grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
}
/* 3. Fluid typography */
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }
/* 4. Fluid images */
img { max-width: 100%; height: auto; }
Node.js / Backend
16. How does Node.js handle concurrency without threads?
Node.js uses an event-driven, non-blocking I/O model. When an I/O operation (disk, network) starts, Node registers a callback and moves on. The OS signals completion; the event loop picks up the callback.
┌──────────────────────────────────┐
│ Event Loop │
│ timers → I/O → poll → check │
└──────────────────┬───────────────┘
│ callbacks
libuv thread pool
(disk I/O, DNS, crypto)
CPU-bound work (image resizing, crypto) blocks the event loop. Use worker_threads or offload to a separate service.
17. What is middleware in Express.js?
Middleware is a function with signature (req, res, next) that sits in the request-response pipeline.
// Logger middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // pass to next middleware/route
});
// Error-handling middleware (4 args — must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({ error: err.message });
});
Middleware executes in order of registration. Forgetting next() hangs the request.
18. How do you structure a Node.js/Express REST API?
src/
app.js ← Express setup, global middleware
server.js ← HTTP server, port binding
routes/
users.js ← /users routes → controller calls
controllers/
users.js ← business logic
services/
userService.js ← data access / external calls
models/
User.js ← Prisma/Mongoose model
middleware/
auth.js ← JWT verify
validate.js ← Zod/Joi input validation
utils/
errors.js ← Custom error classes
Controllers stay thin. Business logic lives in services. This makes testing services without HTTP easy.
19. How do you validate request input in Node.js?
Use a schema validation library at the controller boundary:
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
role: z.enum(['admin', 'user']).default('user'),
});
export async function createUser(req, res, next) {
const parsed = createUserSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: parsed.error.flatten() });
}
// parsed.data is fully typed and validated
const user = await userService.create(parsed.data);
res.status(201).json({ data: user });
}
20. How do you handle errors in async Express routes?
In Express 4, unhandled promise rejections in async handlers do not reach the error middleware by default.
// Wrapper to forward async errors
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
const user = await userService.findById(req.params.id);
if (!user) throw Object.assign(new Error('Not found'), { status: 404 });
res.json({ data: user });
}));
// Express 5 (native async support — no wrapper needed)
Databases
21. When do you choose SQL vs NoSQL?
| Criteria | SQL (PostgreSQL, MySQL) | NoSQL (MongoDB, Redis) |
|---|---|---|
| Data shape | Fixed schema, relational | Flexible / dynamic schema |
| Queries | Complex JOINs, aggregations | Simple lookups, key-value |
| Consistency | ACID transactions | Eventual consistency (tunable) |
| Scale | Vertical + read replicas | Horizontal sharding |
| Use cases | Finance, ERP, e-commerce | Catalogs, sessions, real-time, logs |
Default to SQL. It handles most use cases with better query flexibility and mature tooling.
22. What is a database index and when should you add one?
An index is a data structure (usually a B-tree) that lets the database find rows without scanning the whole table.
-- Without index: full table scan (O(n))
SELECT * FROM orders WHERE user_id = 42;
-- With index: B-tree lookup (O(log n))
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Composite index for common filter+sort
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
Add indexes on:
- Foreign keys
- Columns in WHERE, ORDER BY, GROUP BY
- Columns in JOIN conditions
Avoid over-indexing: each index slows down INSERT/UPDATE/DELETE.
23. What is a database transaction?
A transaction is a unit of work that is either fully committed or fully rolled back — guaranteeing ACID properties.
// Prisma transaction example
const [order, payment] = await prisma.$transaction([
prisma.order.create({ data: orderData }),
prisma.payment.create({ data: paymentData }),
]);
// If payment.create throws, order.create is also rolled back
ACID:
- Atomicity — all or nothing
- Consistency — database stays in valid state
- Isolation — concurrent transactions don't see each other's intermediate state
- Durability — committed data survives crashes
24. What is N+1 query problem and how do you fix it?
// N+1 problem: 1 query for posts + N queries for each author
const posts = await Post.findAll();
for (const post of posts) {
post.author = await User.findById(post.userId); // 1 query × N posts
}
// Fix 1: JOIN (eager loading in Prisma)
const posts = await prisma.post.findMany({
include: { author: true }, // single JOIN query
});
// Fix 2: DataLoader batching (for GraphQL resolvers)
const userLoader = new DataLoader(async (ids) =>
User.findAll({ where: { id: ids } })
);
25. What are the differences between PostgreSQL and MongoDB?
| PostgreSQL | MongoDB | |
|---|---|---|
| Model | Relational (tables) | Document (JSON/BSON) |
| Schema | Enforced (migrations) | Flexible (schemaless) |
| Joins | Native SQL JOINs | $lookup (limited) |
| Transactions | Full ACID, multi-table | Multi-document ACID (4.0+) |
| Full-text search | Built-in (tsvector) |
Atlas Search / basic |
| Best for | Structured relational data | Variable-shape documents, rapid iteration |
APIs
26. What makes a REST API "RESTful"?
REST constraints:
| Constraint | Meaning |
|---|---|
| Stateless | Each request has all info needed; server holds no client session |
| Uniform interface | Standard HTTP verbs + resource-based URLs |
| Resource-based | Nouns in URLs, not actions (/users not /getUsers) |
| Representations | JSON/XML response; client and server decoupled |
| HATEOAS (optional) | Response includes links to related actions |
GET /users ← list
POST /users ← create
GET /users/:id ← read
PUT /users/:id ← replace
PATCH /users/:id ← partial update
DELETE /users/:id ← remove
27. How do you version a REST API?
| Strategy | Example | Pros / Cons |
|---|---|---|
| URL segment | /api/v1/users |
Simple, cache-friendly; pollutes URL namespace |
| Query param | /api/users?version=1 |
Easy to test; not standard |
| Header | Accept: application/vnd.api+json;version=1 |
Clean URLs; harder to test in browser |
URL versioning is the most common and easiest to understand.
28. What is the difference between REST and GraphQL?
| REST | GraphQL | |
|---|---|---|
| Endpoint count | Many (/users, /posts) |
One (/graphql) |
| Data fetching | Fixed shape per endpoint | Client specifies exact fields |
| Over/under-fetching | Common | Avoided by design |
| Versioning | URL versions (v1, v2) |
Schema evolution with deprecation |
| Caching | HTTP caching out of the box | More complex (persisted queries) |
| Learning curve | Low | Higher |
Use REST for simple public APIs. Consider GraphQL for complex, data-driven UIs with many different clients.
Authentication & Security
29. How does JWT authentication work?
Client Server
│── POST /login (email+pass) ──▶│
│ │ verify credentials
│◀── 200 { token: "eyJ..." } ───│ sign JWT with secret
│ │
│── GET /profile │
│ Authorization: Bearer eyJ…──▶│ verify JWT signature
│◀── 200 { user } ──────────────│ decode payload, load user
JWT structure: header.payload.signature — all base64-encoded.
import jwt from 'jsonwebtoken';
// Sign
const token = jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, {
expiresIn: '15m', // short-lived access token
});
// Verify middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
30. What is the difference between authentication and authorization?
| Authentication | Authorization | |
|---|---|---|
| Question | Who are you? | What can you do? |
| Mechanism | Password, OAuth, biometrics | RBAC, ABAC, policies |
| When | Login | Every request |
| Failure | 401 Unauthorized | 403 Forbidden |
// Authentication — prove identity
app.post('/login', async (req, res) => { /* verify credentials */ });
// Authorization — check permissions
function requireRole(role) {
return (req, res, next) => {
if (req.user.role !== role) return res.status(403).json({ error: 'Forbidden' });
next();
};
}
router.delete('/users/:id', requireAuth, requireRole('admin'), deleteUser);
31. How do you prevent SQL injection?
Never concatenate user input into SQL strings.
// VULNERABLE
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
// Attacker sends: ' OR '1'='1 → dumps all users
// SAFE: parameterized query (pg)
const { rows } = await pool.query(
'SELECT * FROM users WHERE email = $1',
[req.body.email]
);
// SAFE: ORM with params (Prisma)
const user = await prisma.user.findUnique({
where: { email: req.body.email },
});
32. How do you prevent XSS attacks?
XSS (Cross-Site Scripting) happens when untrusted data is rendered as executable code.
| Prevention | Implementation |
|---|---|
| Output encoding | Frameworks (React, Vue) escape by default. Never dangerouslySetInnerHTML without sanitizing |
| Content Security Policy | Content-Security-Policy: default-src 'self' header |
| Sanitize HTML input | Use DOMPurify for user-generated rich text |
| HTTP-only cookies | Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict |
33. What is CSRF and how do you prevent it?
CSRF (Cross-Site Request Forgery) tricks a logged-in user's browser into sending requests to your server.
Attacker's page:
<img src="https://bank.com/transfer?to=attacker&amount=1000">
Browser auto-sends cookies → bank executes transfer
Prevention:
| Method | How |
|---|---|
| SameSite cookies | SameSite=Strict or Lax blocks cross-site cookie sending |
| CSRF tokens | Include a random token in forms; verify server-side |
| Custom headers | AJAX with X-Requested-With header — cross-origin can't add custom headers |
System Design
34. What is a cache and what caching strategies exist?
A cache stores frequently accessed data closer to the application to reduce latency and database load.
| Strategy | How | Use case |
|---|---|---|
| Cache-aside (lazy) | App checks cache first, reads DB on miss, populates cache | General reads |
| Write-through | Write to cache and DB simultaneously | Data consistency |
| Write-behind | Write to cache; async flush to DB | Write-heavy workloads |
| Read-through | Cache handles DB reads automatically | Transparent caching |
| TTL expiry | Auto-expire stale data after N seconds | Time-sensitive data |
// Cache-aside with Redis
async function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);
await redis.setex(`user:${id}`, 3600, JSON.stringify(user)); // TTL: 1 hour
return user;
}
35. How would you scale a web application?
| Technique | What it addresses |
|---|---|
| Horizontal scaling | Add more application servers behind a load balancer |
| Database read replicas | Distribute read traffic to replica databases |
| Caching (Redis, CDN) | Reduce database and origin server load |
| Async queues | Offload slow work (email, image processing) to background workers |
| Database sharding | Partition large datasets across multiple databases |
| CDN | Serve static assets from edge nodes close to users |
| Stateless services | Store session in Redis; any instance can serve any request |
36. What is the difference between monolithic and microservices architecture?
| Monolith | Microservices | |
|---|---|---|
| Deployment unit | Single artifact | Many independent services |
| Scaling | Scale the whole app | Scale individual services |
| Development | Simpler at small scale | Independent teams + deployments |
| Communication | Function calls | HTTP/gRPC/message queues |
| Data | Single database | Database per service |
| Complexity | Low (start) | High (distributed system) |
Start with a monolith. Move to microservices when you have clear service boundaries and autonomous teams. Premature microservices create distributed monoliths.
37. What is a message queue and when do you use one?
A message queue decouples producers from consumers, enabling async processing.
User uploads image → API publishes to queue → Worker picks up → resize/compress → store
▲ 200 immediate ▲ processes in background
Use when:
- Task takes longer than ~200ms (don't block HTTP response)
- Work needs retry on failure
- Processing rate needs to differ from request rate
- Multiple consumers process the same event (fan-out)
Popular: BullMQ (Node.js), Celery (Python), RabbitMQ, Kafka.
DevOps Basics
38. What is Docker and why do full-stack developers use it?
Docker packages an application and all its dependencies into a container — a lightweight, isolated environment that runs identically everywhere.
# Multi-stage build for a Node.js API
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]
Full-stack benefits:
- Consistent dev/staging/prod environments
docker-composeruns frontend + API + database locally with one command- Simplifies deployment to any cloud provider
39. What is CI/CD and what should a full-stack pipeline include?
CI = Continuous Integration (automated build + test on every push)
CD = Continuous Delivery/Deployment (automated release to staging/production)
# GitHub Actions full-stack pipeline
name: CI/CD
on: [push]
jobs:
test:
steps:
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm test -- --coverage
build:
needs: test
steps:
- run: npm run build
- run: docker build -t my-app .
- run: docker push my-app
deploy:
needs: build
if: github.ref == 'refs/heads/main'
steps:
- run: kubectl set image deployment/app app=my-app:${{ github.sha }}
40. How do you manage environment variables securely?
| Environment | Storage | Access |
|---|---|---|
| Local dev | .env file (git-ignored) |
process.env.VAR |
| CI/CD | GitHub Secrets / GitLab Variables | Injected at runtime |
| Production | Secret manager (Vault, AWS Secrets Manager) | Fetched at startup |
// Validate required env vars at startup (fail fast)
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'REDIS_URL'];
for (const key of requiredEnvVars) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
Never: commit .env files, hardcode secrets in source code, log secret values.
Performance
41. What are Core Web Vitals and how do you improve them?
| Metric | Measures | Target |
|---|---|---|
| LCP (Largest Contentful Paint) | Loading performance | < 2.5s |
| INP (Interaction to Next Paint) | Interactivity | < 200ms |
| CLS (Cumulative Layout Shift) | Visual stability | < 0.1 |
Improving LCP: preload hero image, serve from CDN, use priority on Next.js images.
Improving INP: move work off main thread, use web workers, avoid long tasks (>50ms).
Improving CLS: set width/height on images, reserve space for ads/async content.
42. How do you optimize React application performance?
// 1. Code splitting — only load what's needed
const Dashboard = React.lazy(() => import('./Dashboard'));
// 2. Virtualize long lists
import { FixedSizeList } from 'react-window';
<FixedSizeList height={400} itemCount={10000} itemSize={50}>
{({ index, style }) => <Row style={style} item={items[index]} />}
</FixedSizeList>
// 3. Avoid anonymous objects/functions in JSX
// ❌ New reference every render
<Button onClick={() => handleClick(id)} style={{ color: 'red' }} />
// ✅ Stable references
const style = useMemo(() => ({ color: 'red' }), []);
const onClick = useCallback(() => handleClick(id), [id]);
<Button onClick={onClick} style={style} />
Advanced Full-Stack
43. How do you implement real-time features?
| Technology | Transport | Use case | Complexity |
|---|---|---|---|
| WebSockets | Persistent TCP | Chat, live collaboration, gaming | Medium |
| Server-Sent Events | HTTP one-way | Notifications, live feeds, progress | Low |
| Long polling | HTTP repeated requests | Simple updates, fallback | Low |
| WebRTC | P2P UDP | Video/audio calls | High |
// Server-Sent Events (simplest for one-way updates)
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
}, 1000);
req.on('close', () => clearInterval(interval));
});
44. How do you implement file uploads securely?
import multer from 'multer';
import path from 'path';
const upload = multer({
storage: multer.memoryStorage(), // buffer, don't write to disk
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max
fileFilter: (req, file, cb) => {
const allowed = ['.jpg', '.jpeg', '.png', '.webp'];
const ext = path.extname(file.originalname).toLowerCase();
if (!allowed.includes(ext)) {
return cb(new Error('Only images are allowed'));
}
cb(null, true);
},
});
// Then upload to S3/Cloudinary — never serve uploaded files as-is
router.post('/upload', requireAuth, upload.single('file'), async (req, res) => {
const url = await uploadToS3(req.file.buffer, req.file.mimetype);
res.json({ url });
});
45. How do you handle pagination in a REST API?
| Strategy | How | Use case |
|---|---|---|
| Offset/limit | ?page=2&limit=20 |
Simple, UI page numbers |
| Cursor-based | ?after=cursor123&limit=20 |
Infinite scroll, live data |
| Keyset | ?after_id=500&limit=20 |
High-performance, no skips |
// Cursor pagination (no N+1, stable under inserts)
router.get('/posts', async (req, res) => {
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const cursor = req.query.after; // last seen ID
const posts = await prisma.post.findMany({
take: limit + 1,
...(cursor && { cursor: { id: cursor }, skip: 1 }),
orderBy: { createdAt: 'desc' },
});
const hasMore = posts.length > limit;
res.json({
data: posts.slice(0, limit),
nextCursor: hasMore ? posts[limit - 1].id : null,
});
});
46. How do you implement rate limiting?
import rateLimit from 'express-rate-limit';
// Global rate limit
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
legacyHeaders: false,
}));
// Stricter limit on auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 1000,
max: 5, // 5 login attempts per minute
message: { error: 'Too many attempts, try again in 1 minute' },
});
app.post('/login', authLimiter, loginHandler);
For distributed systems, use Redis-backed rate limiting (rate-limit-redis) so limits are shared across all instances.
47. How do you design a database schema for a blog?
-- Users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Posts
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
author_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
slug VARCHAR(255) UNIQUE NOT NULL,
title VARCHAR(500) NOT NULL,
body TEXT NOT NULL,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Tags (many-to-many)
CREATE TABLE tags (id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL);
CREATE TABLE post_tags (
post_id INT REFERENCES posts(id) ON DELETE CASCADE,
tag_id INT REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
-- Key indexes
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_published ON posts(published_at DESC) WHERE published_at IS NOT NULL;
Behavioral & Trade-offs
48. How do you decide between building vs buying a feature?
Build when:
- It's a core differentiator (unique business logic)
- No good solution exists
- Long-term maintenance cost of integration is high
Buy/use SaaS when:
- Commodity feature (auth, email, payments, monitoring)
- Building would take months and vendors solve it fully
- Compliance burden (PCI, HIPAA) belongs with the vendor
auth → Auth0 / Supabase Auth (don't build your own)
payments → Stripe (never PCI scope yourself)
email → Resend / SendGrid
monitoring → Datadog / Sentry
search → Algolia / Elasticsearch (only if needed at scale)
49. Describe a time you improved application performance.
Use the STAR framework:
| Step | What to cover |
|---|---|
| Situation | App was slow — e.g., dashboard took 8s to load |
| Task | Your responsibility to investigate and fix |
| Action | Profiled → found N+1 queries → added eager loading + Redis cache |
| Result | Load time reduced from 8s to 400ms; database load dropped 80% |
Tips: give concrete numbers, mention what tools you used (Lighthouse, New Relic, EXPLAIN ANALYZE), acknowledge trade-offs.
50. How do you approach a new full-stack feature end-to-end?
1. Understand requirements
├── What problem does this solve?
├── What are the edge cases?
└── What are the acceptance criteria?
2. Design
├── API contract (endpoint, request/response shape)
├── Database schema changes
└── UI wireframe / component breakdown
3. Implement
├── Write migration → run locally
├── Build API endpoint (TDD: tests first)
├── Build React component (mock data first)
└── Wire together: real API + real component
4. Review & verify
├── Lint, type-check, unit tests
├── Manual testing on dev env
└── PR with context: screenshots, test plan, API docs
5. Deploy
├── Staging deploy + QA
└── Production deploy with monitoring
Anti-patterns to avoid
| Anti-pattern | Problem | Fix |
|---|---|---|
| Business logic in controllers | Untestable, fat controllers | Move to service layer |
| No input validation | Security vulnerabilities | Validate at every API boundary |
| Storing secrets in code | Security breach | Use env vars + secret manager |
SELECT * everywhere |
Over-fetching, schema coupling | Select only needed columns |
| No error handling in async code | Silent failures | Always try/catch or .catch() |
| Synchronous blocking in Node | Event loop starvation | Use async I/O or worker threads |
Missing await |
Unhandled promise, wrong type | TypeScript strict mode catches this |
| Unbounded lists | Memory/performance crash | Always paginate collection endpoints |
Full-stack vs related roles
| Role | Front-end weight | Back-end weight | Infra weight |
|---|---|---|---|
| Front-end developer | ████████████ | ██ | █ |
| Back-end developer | ██ | ████████████ | ██ |
| Full-stack developer | ██████ | ██████ | ███ |
| DevOps / SRE | █ | ███ | ████████████ |
| Solutions architect | ████ | ████ | ████████ |
FAQ
Q: Is a full-stack developer expected to be expert in both front-end and back-end?
A: No. Full-stack devs are expected to be competent across the stack, not expert in every area. You'll typically have a natural lean toward one side. Depth matters more than breadth at senior levels.
Q: What tech stack should I learn as a full-stack developer in 2025?
A: The most in-demand combination is TypeScript + React + Node.js + PostgreSQL (the "PERN" stack). Next.js is the dominant React framework for full-stack work. Prisma is the most ergonomic Node.js ORM.
Q: How do I answer "Tell me about a full-stack project you built"?
A: Pick a project where you made meaningful decisions across the stack. Explain: the problem it solved, the key technical decisions (database choice, auth approach, deployment), the trade-offs you made, and what you'd change now.
Q: What system design topics should a full-stack developer know?
A: Focus on: horizontal scaling, caching strategies (Redis, CDN), async queues (BullMQ), relational database design, API versioning, and stateless services. You don't need to know Kafka partitioning or distributed consensus at a full-stack developer level.
Q: How is a full-stack developer interview different from a front-end interview?
A: Expect additional questions on: database design, API security, backend performance, environment/config management, and at least one system design question. Algorithmic questions (LeetCode) are similar in depth but may include more backend-oriented problems.
Q: What salary can a full-stack developer expect in 2025?
A: In the US: junior $70-95k, mid-level $100-140k, senior $140-200k, staff/lead $170-250k+. Full-stack typically commands 10-15% higher than equivalent single-track roles. Remote roles are competitive globally — UK/EU senior full-stack runs £70-110k / €80-130k.