MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Async Pipelines
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

Async Pipelines

Reading45 min

Moving work out of the request cycle is easy. Moving it out reliably, so a crash halfway through doesn't corrupt anything or lose an event, is the part that separates "I used Celery to send an email" from "I understand distributed failure modes." This is a favorite senior-level interview area: multi-stage pipelines, dead letter queues, and the outbox pattern.

Three shapes of async work

Fire-and-forget. A request triggers work and doesn't wait for it: a Celery task or a FastAPI BackgroundTasks call. The simplest case.

Fan-out / fan-in. One input splits into many parallel subtasks, then the results are combined. Resize an upload into 5 sizes at once, mark it "processed" once all 5 finish.

Pipeline (multi-stage). Stage N's output is stage N+1's input, and each stage scales and retries independently: upload, virus scan, transcode, thumbnail, notify.

Python
from celery import Celery, chain

app = Celery("pipeline", broker="redis://localhost:6379/0")

@app.task(bind=True, max_retries=3, default_retry_delay=10)
def scan_upload(self, file_id: str) -> str:
    if not virus_scan(file_id):
        raise ValueError(f"file {file_id} failed virus scan")
    return file_id

@app.task(bind=True, max_retries=3, default_retry_delay=10)
def transcode(self, file_id: str) -> str:
    try:
        run_transcode(file_id)
        return file_id
    except TranscodeError as exc:
        raise self.retry(exc=exc)

@app.task
def generate_thumbnail(file_id: str) -> str:
    make_thumbnail(file_id)
    return file_id

@app.task
def notify_owner(file_id: str) -> None:
    send_notification(file_id, "Your upload is ready")

def start_pipeline(file_id: str):
    pipeline = chain(scan_upload.s(file_id), transcode.s(), generate_thumbnail.s(), notify_owner.s())
    pipeline.apply_async()

Each stage is its own task with its own retry policy. A transient transcode failure retries on its own, without re-running the (expensive) virus scan that already succeeded. That independence is the whole point of building a pipeline instead of one giant function: it isolates each stage's failure domain and retry policy.

Remember: a pipeline's real value is isolating each stage's failures. A retry in stage 3 should never have to redo stages 1 and 2.

Sign in to try this knowledge check.

Partial failures: what state is left behind

"How do you handle partial failures in a pipeline" is really asking: what happens when stage 3 of 5 fails, and what does the system look like afterward?

Three rules:

  1. Every stage must be idempotent. A retry re-runs the stage, so running generate_thumbnail twice for the same file must not create two thumbnails. Use an upsert (ON CONFLICT DO UPDATE in Postgres) instead of a blind insert.
  2. Track stage status explicitly, in a table, not by inferring it from queue state, which disappears the moment a task finishes or fails. A status row lets you query "what's stuck right now" and resume a stage from its last completed step instead of starting over.
  3. Decide per failure: retry, skip, or fail the whole pipeline. Not every failure deserves the same response.
Python
@app.task(bind=True, max_retries=3)
def transcode(self, file_id: str) -> str:
    run = get_or_create_run(file_id, stage="transcode")
    run.status, run.attempts = StageStatus.RUNNING, run.attempts + 1
    db.commit()
    try:
        run_transcode(file_id)
    except UnsupportedFormatError as exc:
        run.status, run.error = StageStatus.FAILED, str(exc)   # not transient — don't retry
        db.commit()
        raise
    except TranscodeTimeoutError as exc:
        db.commit()
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)   # transient — worth retrying
    else:
        run.status = StageStatus.DONE
        db.commit()
        return file_id

The distinction between a permanent failure (bad input, retrying never helps) and a transient one (a network blip, worth retrying with backoff) is the single most important decision in any pipeline. Retrying a permanent failure just burns queue capacity and delays the signal that a human actually needs to look at it.

Remember: classify every failure as permanent or transient before deciding to retry. Retrying a permanent failure wastes capacity and delays the alert that a human is actually needed.

Sign in to try this knowledge check.

Dead letter queues: a graveyard needs a way back out

A dead letter queue (DLQ) is where a task lands after exhausting its retries or hitting a permanent failure, instead of vanishing or retrying forever.

Python
from celery.signals import task_failure

@task_failure.connect
def handle_task_failure(sender=None, task_id=None, exception=None, args=None, kwargs=None, **extra):
    send_to_dead_letter_queue(
        task_name=sender.name, task_id=task_id,
        payload={"args": args, "kwargs": kwargs}, error=str(exception),
    )

With RabbitMQ or SQS-style brokers, a DLQ is often a first-class feature: configure a max-retry count and a target queue, and the broker moves the message there automatically. With Redis and Celery, it's usually rolled by hand, as above, or via acks_late plus a max-retries exception handler.

What actually matters for the interview: a DLQ entry needs a replay path. A write-only DLQ is just a failure graveyard. Build, or at least describe, a way to inspect a dead-lettered task, fix the underlying issue, and re-enqueue it, often an admin endpoint or CLI command that reads the stored arguments and calls the task again.

Remember: a dead letter queue without a replay path is just a place failures go to be forgotten. The whole point is inspecting, fixing, and re-enqueuing.

Sign in to try this knowledge check.

The outbox pattern: writing to a database and publishing an event, atomically

The problem: you often need to update the database and publish an event together, for example creating an order and publishing OrderCreated. Done as two separate operations, there's a window where one succeeds and the other fails: the database commits and the process crashes before the publish, or the publish succeeds and the transaction then rolls back. Either way, your database and your event consumers now disagree about reality.

The outbox pattern fixes this by writing the event into an outbox table, in the same database transaction as the business data. A separate relay process reads unpublished rows and publishes them, retrying until it succeeds. Because the outbox write is transactional with the business write, the event is guaranteed to exist if and only if the business data was actually committed.

sql
CREATE TABLE outbox (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    aggregate_id TEXT NOT NULL,
    event_type TEXT NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    published_at TIMESTAMPTZ
);
Python
def create_order(db_session, order_data: dict):
    with db_session.begin():                    # one transaction
        order = Order(**order_data)
        db_session.add(order)
        db_session.flush()                        # get order.id without committing
        db_session.add(Outbox(
            aggregate_id=str(order.id), event_type="OrderCreated",
            payload={"order_id": str(order.id), "total": order.total},
        ))
    # both rows commit together, or neither does
    return order
Python
def relay_outbox_events(db_session, publisher):
    unpublished = db_session.query(Outbox).filter(Outbox.published_at.is_(None)).order_by(Outbox.created_at).limit(100)
    for event in unpublished:
        try:
            publisher.publish(event.event_type, event.payload)
            event.published_at = datetime.now(UTC)
            db_session.commit()
        except PublishError:
            db_session.rollback()
            break   # stop here, retry this batch next tick, preserving order

This is sometimes paired with change data capture (reading the database's own write-ahead log, instead of polling) to catch the outbox insert the instant it commits, worth naming as the "at scale" version if asked.

The best guarantee outbox plus retry can offer is at-least-once delivery, since a crash between publishing and marking published_at can still cause a duplicate publish. Pair it with an idempotent consumer, keyed on event ID, to close the loop:

Python
def handle_order_created(event: dict, db_session):
    if db_session.query(ProcessedEvent).filter_by(event_id=event["event_id"]).first():
        return  # duplicate delivery, safely ignored
    with db_session.begin():
        fulfill_order(event["order_id"])
        db_session.add(ProcessedEvent(event_id=event["event_id"], processed_at=datetime.now(UTC)))

That combination, a transactional outbox on the producer side paired with an idempotent, event-ID-keyed handler on the consumer side, is what "reliable event publishing" means in practice: not zero duplicates, but zero lost events, and safe handling of the duplicates that do slip through.

Remember: the outbox table is written in the same transaction as the business data, so the event's existence is tied to the write actually committing. A relay process publishes it afterward, at-least-once, which is why the consumer still needs to be idempotent.

Sign in to try this knowledge check.
PreviousKafka and Message QueuesNextDistributed Systems Basics

Course progress

0%
0 of 177 modules complete

On this page

  • Three shapes of async work
  • Partial failures: what state is left behind
  • Dead letter queues: a graveyard needs a way back out
  • The outbox pattern: writing to a database and publishing an event, atomically