Toolmingo
Guides25 min read

50 Full Stack Developer Interview Questions (With Answers)

Top full stack developer interview questions with detailed answers and code examples — covering JavaScript, TypeScript, React, Node.js, databases, REST APIs, auth, system design, and DevOps.

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:

  1. Element type changes → unmount old, mount new subtree
  2. Same type → update props in place
  3. 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-compose runs 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.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools