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
Celery and Background Workers
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

Celery and Background Workers

Reading60 min

Picture a waiter who takes your order, then stands at your table while the kitchen cooks it, blocking every other customer from being served. That is a web server that does slow work inside the request itself: sending an email, generating a report, running a model. Once one request takes 10 seconds, everything behind it queues up too. A background worker is the fix: the waiter drops your order in the kitchen and immediately moves to the next table.

Celery is the standard Python answer to this problem, and interviews push past "I used @shared_task" into the two things that actually break in production: a task running twice, and a worker dying mid-task. This lesson builds both answers with real mechanisms, then covers routing, chains, and how Celery compares to a couple of common alternatives.

Celery's moving pieces

Celery has three parts:

  • Broker (Redis or RabbitMQ): a message queue holding tasks waiting to run. Your app pushes a task message here; it never runs the task itself.
  • Worker(s): separate processes that pull messages off the broker and execute the task function. You can run many workers, on many machines, all consuming from the same queue.
  • Result backend (optional: Redis, a database): stores a task's return value and state, if something later needs to check on it.
Python
from celery import Celery

app = Celery(
    "myproject",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",   # a separate Redis DB index from the broker
)

The web app never talks to a worker directly. It serializes the task's name and arguments into a message and publishes it to the broker. Some worker, possibly on a different machine, picks it up whenever it's free. That decoupling is the entire point: the request returns immediately, and the real work scales independently of your web tier.

Remember: the broker holds pending work, the worker runs it, and the result backend (if configured) remembers what happened. The web app only ever talks to the broker.

Sign in to try this knowledge check.

Retry with backoff, and why jitter matters

Python
@app.task(
    bind=True,
    max_retries=5,
    autoretry_for=(requests.RequestException,),
    retry_backoff=True,        # 1s, 2s, 4s, 8s...
    retry_backoff_max=60,
    retry_jitter=True,         # randomize so many failed tasks don't retry in lockstep
)
def send_webhook(self, url: str, payload: dict):
    response = requests.post(url, json=payload, timeout=5)
    response.raise_for_status()
    return response.status_code

retry_jitter is the detail interviewers listen for. Without it, if 1,000 tasks fail at the same moment, say a downstream service blips, they all retry at exactly the same backed-off intervals, producing a synchronized retry storm that can re-crash the very service they're retrying against. Jitter spreads that storm out over time instead.

For cases needing custom logic instead of autoretry_for:

Python
@app.task(bind=True, max_retries=5)
def process_payment(self, payment_id: int):
    try:
        _charge_provider(payment_id)
    except TransientProviderError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)
    except PermanentProviderError:
        raise  # this will never succeed no matter how many times you retry — fail loudly

Remember: add jitter to every backoff. Without it, a thousand tasks failing together retry together, turning a blip into a second, synchronized outage.

Sign in to try this knowledge check.

Idempotency: the number one Celery interview question

The premise behind "how do you ensure idempotency" is that Celery's default guarantee is at-least-once delivery, not exactly-once. A worker can execute a task, crash before acknowledging it to the broker, and the broker redelivers it to another worker: now it has run twice.

The fix is making the task's effect idempotent, not trying to prevent redelivery, which you can't reliably do:

Python
@app.task(bind=True, max_retries=3)
def charge_customer(self, order_id: int, idempotency_key: str):
    # dedup guard BEFORE the side effect, not after
    was_processed = redis_client.set(f"processed:{idempotency_key}", "1", nx=True, ex=86400)
    if not was_processed:
        return  # already handled this exact request — no-op, not an error

    order = Order.objects.get(id=order_id)
    payment_provider.charge(order.total, reference=idempotency_key)
    order.status = "paid"
    order.save()

Two standard mechanisms: an idempotency key plus a dedup store (shown above, checked before the side effect happens), or natural idempotency, designing the operation so running it twice has the same result as running it once. UPDATE orders SET status = 'shipped' WHERE id = %s is naturally idempotent. INSERT INTO shipments ... is not, unless you add a unique constraint on order_id and catch the conflict.

Remember: you can't reliably stop a redelivery, so the dedup check happens before the side effect, using a key generated once by the caller, not once per retry.

Sign in to try this knowledge check.

What happens when a worker dies

This depends entirely on acknowledgment mode.

  • Early ack (the default): the worker acks the message as soon as it starts the task. If the worker dies mid-task, the message is already gone from the broker: the task is silently lost. Fine for best-effort, non-critical work.
  • Late ack (task_acks_late=True): the worker acks only after the task finishes. If the worker crashes mid-task, the broker never got an ack, so it redelivers the message to another worker. Safer for critical work, but it means a task that crashes the worker itself will be retried, which is exactly why the task body must be idempotent.
Python
app.conf.task_acks_late = True
app.conf.worker_prefetch_multiplier = 1   # don't let a slow/crashing worker hoard several tasks at once

worker_prefetch_multiplier = 1 matters alongside late-ack: with the default prefetch, a worker reserves several tasks at once, and if it crashes, all of those reserved-but-unstarted tasks get redelivered together, potentially all landing on one already-busy worker.

Remember: early ack can silently lose a task on worker crash; late ack redelivers it but requires the task to be idempotent. Pick late ack plus prefetch_multiplier=1 for anything that must actually complete.

Sign in to try this knowledge check.

Task routing and priority queues

By default every task goes to one queue and any worker can pick it up. Routing sends different tasks to different queues, so a dedicated pool of workers can handle slow tasks (video processing) separately from fast ones (sending an email). Otherwise, one slow task blocks a worker that could have cleared ten fast ones in the meantime.

Python
task_routes = {
    "orders.tasks.send_receipt_email": {"queue": "fast"},
    "orders.tasks.generate_invoice_pdf": {"queue": "slow"},
}
bash
celery -A myproject worker -Q fast --concurrency=8 -n fast@%h
celery -A myproject worker -Q slow --concurrency=2 -n slow@%h

Celery has no true priority within a queue on Redis by default; it's FIFO. To get priority, either run separate queues per priority tier, as shown above, or switch to RabbitMQ as the broker, which supports a real per-message priority field. The interview answer: "priority" in Celery is almost always implemented as queue separation, not a priority field, because the default broker doesn't support message priority at all.

Chains run tasks sequentially, each one receiving the previous task's return value. Chords run a group of tasks in parallel, then fire a callback once every one of them finishes, with their results collected as a list.

Python
from celery import chain, chord

pipeline = chain(fetch_data.s(source_id=1), transform.s(), save_result.s())
pipeline.apply_async()

job = chord(
    [resize_image.s(image_id=99, size=s) for s in ("small", "medium", "large")],
    notify_all_sizes_ready.s(upload_id=42),
)
job.apply_async()

A chord's callback is itself just another task. Celery tracks completion with a counter in the result backend, decrementing it as each group member finishes and firing the callback at zero. This is why a chord requires a result backend: without one, there is no way to know when the group is actually done.

Remember: "priority" in Celery usually means separate queues, not a priority field, because the default broker (Redis) doesn't support one. A chord needs a result backend to know when its group is finished.

Sign in to try this knowledge check.

Custom task states, periodic tasks, and the result backend

Beyond Celery's built-in states, update_state reports custom progress, essential for a long task a frontend needs to show progress for.

Python
@app.task(bind=True)
def generate_report(self, report_id):
    rows = fetch_rows(report_id)
    for i, row in enumerate(rows):
        process_row(row)
        if i % 100 == 0:
            self.update_state(state="PROGRESS", meta={"current": i, "total": len(rows)})
    return {"report_id": report_id, "rows_processed": len(rows)}

The result backend (commonly Redis or Postgres) is where Celery writes task state and return values, separate from the broker, which only queues the task messages themselves. Without a result backend, task.delay() still runs the task, but AsyncResult.status stays PENDING forever and .get() hangs, because there's nowhere for the answer to be written.

Periodic tasks run through celery beat, a separate scheduler process that pushes tasks onto the broker at configured times; actual execution still goes through normal workers.

Python
from celery.schedules import crontab

app.conf.beat_schedule = {
    "cleanup-expired-sessions-every-hour": {
        "task": "myapp.tasks.cleanup_expired_sessions",
        "schedule": crontab(minute=0),
    },
}

Run exactly one beat process. Two of them will double-schedule everything.

Remember: the broker queues messages; the result backend stores outcomes. They're conceptually separate even when both point at the same Redis instance, and only one of them tells you whether a task is done.

Sign in to try this knowledge check.

Choosing between Celery, Temporal, and a custom worker

Celery is not the only option, and knowing when to reach for something else is itself an interview-worthy answer.

Temporal is a workflow orchestration engine. You write a workflow function that calls "activities," the actual side-effecting steps, and Temporal's server durably persists execution state after every step. If a worker crashes mid-workflow, a new worker resumes from the last completed activity, not from the start. That's the specific thing Celery doesn't give you: durable, resumable, multi-step state, plus a UI showing exactly which step a given execution is on. The cost is real: Temporal needs its own server plus Postgres plus a UI, roughly 6-7x heavier at idle than a Redis-plus-worker Celery setup, which matters on a small cluster.

A custom DB-backed worker claims rows from a jobs table using SELECT ... FOR UPDATE SKIP LOCKED, so multiple worker processes never double-process the same row:

Python
async def run_worker(concurrency=5):
    while True:
        with transaction.atomic():
            tasks = list(
                Task.objects.select_for_update(skip_locked=True)
                .filter(status="pending").order_by("created_at")[:concurrency]
            )
        if tasks:
            await asyncio.gather(*[handle(t) for t in tasks])
        else:
            await asyncio.sleep(2)

skip_locked=True is the key detail: a second worker running the same loop skips rows this one already locked, instead of blocking on them. This option needs zero new infrastructure if you already run Postgres, at the cost of hand-rolled retry logic and a throughput ceiling in the low thousands of jobs per second.

Custom DB worker Celery Temporal
Extra infra None (reuses Postgres) Redis Temporal server + Postgres
Retry logic Manual Built in Built in
Multi-step workflows Manual orchestration chain / chord Native, durable, resumable
Setup time Fast Fast Slow: deploy the server stack first

The decision framework: simple, independent tasks (send an email, resize an image) or a small, resource-constrained cluster both point at Celery. A multi-step pipeline needing step-by-step visibility and resumability across a crash, parse, chunk, run AI calls, generate output, points at Temporal. A team that wants low overhead now but may grow into complex workflows later should start with Celery and migrate only the specific workflow that outgrows it, rather than moving the whole system upfront.

Remember: Celery for simple or independent tasks; Temporal for multi-step workflows that must resume from the last completed step after a crash. Start simple and migrate the one workflow that outgrows it, not the whole system.

Sign in to try this knowledge check.

BullMQ: the same idea in Node

BullMQ is a Redis-backed job queue for Node.js, the JavaScript-ecosystem equivalent of Celery. The core idea is identical: move slow work out of the request/response cycle onto workers pulling from a shared queue.

js
import { Queue, Worker } from 'bullmq';

const connection = { host: 'localhost', port: 6379 };
const emailQueue = new Queue('emails', { connection });

await emailQueue.add('welcome-email', { userId: 42 }, {
  attempts: 3,
  backoff: { type: 'exponential', delay: 1000 },
});

new Worker('emails', async (job) => {
  await sendWelcomeEmail(job.data.userId);
}, { connection });

emailQueue.add pushes the job onto a Redis-backed structure and returns immediately, without waiting for the email to send. A separate Worker process, possibly on a different machine, picks it up, runs the callback, and if it throws, BullMQ retries automatically using the configured exponential backoff, up to the attempts limit. What BullMQ adds over a plain Redis list: delayed jobs, automatic retries with backoff, priority queues, and per-queue rate limiting, the same category of feature Celery gives Python. A candidate coming from Python can map BullMQ concepts onto Celery concepts almost one to one: Queue.add is task.delay(), attempts/backoff is autoretry_for/retry_backoff, and a Worker is a Celery worker process.

Remember: BullMQ is Celery's Node equivalent: a Redis-backed queue, a worker pool, and built-in retries with backoff. The concepts transfer almost directly between the two ecosystems.

Sign in to try this knowledge check.
PreviousRedis Advanced PatternsNextKafka and Message Queues

Course progress

0%
0 of 177 modules complete

On this page

  • Celery's moving pieces
  • Retry with backoff, and why jitter matters
  • Idempotency: the number one Celery interview question
  • What happens when a worker dies
  • Task routing and priority queues
  • Custom task states, periodic tasks, and the result backend
  • Choosing between Celery, Temporal, and a custom worker
  • BullMQ: the same idea in Node