← Curriculum 13 · Backend ⏱ 60 min

Module 13 · Stack · Full-Stack

Backend

Backend interviews reward knowing the patterns under the framework — how a request flows, how dependencies are wired, how you don't fall over under load. Frameworks are just implementations of these ideas.

60 min deep read 🎯 10 sections 📊 1 diagram

By the end you'll be able to explain, with conviction:

  • What a framework actually provides, and DI/IoC underneath it.
  • The middleware request pipeline and where cross-cutting concerns live.
  • Connection pooling, async jobs, and robust error handling.

1The role of a backend framework

A framework exists so you write business logic, not plumbing.

A backend framework handles the undifferentiated machinery every web service needs: parsing HTTP requests, routing them to your code, serialising responses, managing the connection lifecycle, and offering hooks for auth, validation, and errors. Without one you'd reinvent all of it — badly. The framework provides structure and convention so a team builds consistently.

The key concept is inversion of control (next section): you don't call the framework; the framework calls your code when a matching request arrives. This is the "Hollywood principle" — don't call us, we'll call you. That inversion is what distinguishes a framework from a library you invoke.

2Spring Boot / Express / FastAPI compared

Three representative styles across ecosystems:

Spring BootExpressFastAPI
LanguageJavaNode.jsPython
StyleBatteries-included, opinionatedMinimal, unopinionatedModern, typed, async
StrengthEnterprise, mature ecosystem, DIFlexible, huge npm ecosystemAuto docs, validation, speed
TradeHeavier, more ceremonyYou assemble structure yourselfYounger ecosystem

The point isn't to crown a winner — it's to articulate the spectrum: Spring gives you everything with strong conventions (great for large enterprise teams); Express gives you a minimal core you compose yourself (maximum flexibility); FastAPI leans on Python type hints to give automatic validation and OpenAPI docs with async performance. Pick by team, ecosystem, and how much structure you want imposed.

3Dependency injection & IoC

This connects straight to Module 01's Dependency Inversion. Inversion of Control is the broad principle: a framework or container, not your code, controls object creation and flow. Dependency Injection is the most common way to achieve it — instead of a class creating its own dependencies (new PaymentService()), they're handed in from outside, usually via the constructor.

// Tightly coupled: hard to test, hard to swap
class OrderService {
    private PaymentService payments = new StripePayments();   // welded in
}

// Injected: the dependency is provided, so it's swappable + testable
class OrderService {
    OrderService(PaymentService payments) { this.payments = payments; }
}

With injection you can pass a fake in tests and a real implementation in production — the class no longer decides.

The payoff is exactly what SOLID promised: loose coupling and testability. The class depends on an abstraction it's given, so you swap implementations freely and inject mocks in tests. A DI container wires the graph for you at startup.

💬 Interview angle

"Dependency injection means a class is handed its dependencies rather than creating them, so it depends on abstractions, not concretions. That's the 'D' in SOLID applied — it makes code loosely coupled and lets me inject fakes for testing."

4The request pipeline — middleware

A request doesn't jump straight to your handler — it flows through a chain of middleware, each a layer that can inspect, modify, or short-circuit it. This is where cross-cutting concerns live, handled once instead of in every handler.

flowchart LR Req[Request] --> M1[Logging] --> M2[Auth] --> M3[Rate limit] --> M4[Validation] --> H[Handler] H --> R[Response flows back through the chain]
Each middleware does one job; auth can reject before the request ever reaches your business logic.

The elegance is the same instinct as API gateways (Module 04) and encapsulation (Module 01): centralise the concern. Authentication, logging, CORS, body parsing, and error handling each become a single reusable layer. Order matters — you authenticate before you hit the handler, and a global error-handling middleware usually sits at the end to catch anything that throws.

5Validation & error handling

Two rules anchor robust backends. First, never trust input (Module 05): validate every incoming request at the boundary — types, ranges, required fields, formats — and reject bad data with a clear 400 before it touches your logic. Schema validators (Joi, Pydantic, Bean Validation) make this declarative.

Second, handle errors centrally and fail meaningfully. A global error handler maps exceptions to appropriate HTTP status codes and a consistent error response shape — never leaking stack traces or internal details to the client (an information-disclosure risk). Distinguish expected errors (validation, not-found → 4xx) from unexpected ones (bugs, downstream failures → 5xx, logged with full context for you). Consistent, structured errors are a hallmark of a professional API.

💬 Interview angle

"I validate all input at the boundary and reject bad requests with a clear 400 before they reach business logic. Errors go through one central handler that maps them to the right status code and a consistent shape — never leaking stack traces, and logging unexpected 5xxs with full context."

6Logging best practices

Logs are how you understand production (Module 09). The practices that signal experience: use structured logging (JSON, not free-text) so logs are queryable; use log levels deliberately (DEBUG/INFO/WARN/ERROR) so you can tune verbosity; and attach a correlation/trace ID to every request so you can follow one journey across services.

The cardinal sin to call out: never log secrets or PII — passwords, tokens, card numbers, personal data. Logs are widely accessible and long-lived, so logging sensitive data is a real breach vector and a compliance violation (Module 05). "Structured, levelled, correlated — and scrubbed of secrets" is the complete answer.

7ORM patterns

ORMs (Module 03) map rows to objects, and two architectural patterns recur. Active Record — the model object carries both data and persistence methods (user.save()); simple and fast to build (Rails, Django). Data Mapper — a separate repository/mapper handles persistence, keeping domain objects ignorant of the database (Hibernate, TypeORM); cleaner separation for complex domains, aligning with clean architecture (Module 06).

Whichever you use, the perennial warning is the N+1 query problem — fetch a list, then lazily trigger a query per item. The fix is eager loading. Knowing your ORM hides SQL you should still understand — and being willing to drop to raw SQL for a hot, complex query — is the senior posture.

8Connection pooling

Opening a database connection is expensive — a TCP handshake plus authentication, tens of milliseconds each. Doing it per request would crush both latency and the database (which caps total connections). A connection pool keeps a set of open connections ready; a request borrows one, uses it, and returns it.

This is a classic resource-reuse pattern, and it's a common interview probe because it reveals whether you've run things at scale. The nuances worth mentioning: pool size must be tuned (too small starves requests, too large overwhelms the DB), and pool exhaustion — all connections borrowed and held — is a frequent cause of mysterious production stalls, often from connections not being released.

💬 Interview angle

"Connections are expensive to open, so a pool keeps a tuned set ready to borrow and return. I size it to balance request throughput against the database's connection limit, and I watch for pool exhaustion — leaked, never-released connections are a classic cause of production stalls."

9Async & background jobs

Not everything should happen inside the request. If a user signs up and you need to send a welcome email, resize an avatar, and sync to a CRM, doing it synchronously makes them wait — and a failure fails the whole request. The fix is to do the essential work synchronously, offload the rest to a background job via a queue (Module 06): the request returns fast, and a worker processes the job independently.

This buys responsiveness, resilience (retry a failed job without re-running the request), and load-leveling (spikes fill the queue instead of overwhelming the service). The tradeoff is eventual consistency — the email arrives a moment later — which is usually fine. Tools: Celery (Python), Sidekiq (Ruby), BullMQ (Node), or a managed queue like SQS.

10Scheduling — cron & friends

Some work runs on a clock, not a request: nightly reports, cleanup, sending reminders, polling. Cron is the classic scheduler; framework-level equivalents include Quartz (Java) and Celery Beat / scheduled tasks (Python). You define a schedule and a job runs at those times.

The distributed gotcha worth raising: if you run multiple instances of your service, a naïve scheduled task fires on every instance — sending duplicate emails or running a report several times. The fix is a distributed lock (e.g. in Redis) or a dedicated scheduler so exactly one instance runs the job. Anticipating that "runs N times in a cluster" failure mode is exactly the kind of foresight interviews reward.

Common trap

A cron job on a horizontally-scaled service runs once per instance unless you coordinate. Use a distributed lock or a single dedicated scheduler so a nightly job doesn't email every customer three times.

Recap — what you can now teach

  • A framework inverts control — it calls your code; DI hands a class its dependencies for loose coupling and testability.
  • The middleware pipeline centralises cross-cutting concerns (auth, logging, validation) in order.
  • Validate all input at the boundary; handle errors centrally without leaking internals.
  • Log structured, levelled, correlated — never secrets; watch the ORM N+1 trap.
  • Connection pools reuse expensive connections; tune size and watch for exhaustion.
  • Offload non-essential work to background jobs; coordinate scheduled tasks across instances.

Self-check

Say each answer out loud before revealing it.

What does "inversion of control" mean for a framework?

Why does dependency injection improve testability?

Where do cross-cutting concerns like auth and logging belong?

Why use a connection pool?

What breaks when a cron job runs on a horizontally-scaled service?