30 Advanced Python Backend Interview Questions for Experienced Developers

Explore 30 advanced Python backend interview questions designed for experienced developers, covering system design, async programming, APIs, and real-world debugging scenarios.
interview questions
Python Backend Q&A Component

Jump to Category

🐍 Core Python & Internals ⚡ Async & Concurrency
🕸️ Web Frameworks & APIs 🏗️ Architecture & System Design
🧪 Testing & Tooling

Core Python & Internals

1. Explain the Global Interpreter Lock (GIL) and its impact on multi-threaded Python programs.

The **Global Interpreter Lock (GIL)** is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecodes at the same time. This means that even on a multi-core processor, only one thread can execute Python code at once.

Its impact is most significant on **CPU-bound** multi-threaded programs, as they can’t achieve true parallelism. For **I/O-bound** programs (like web servers), the GIL is less of a bottleneck because it is released during I/O operations, allowing other threads to run.

Read a detailed explanation on Real Python.

2. How does garbage collection work in CPython?

CPython’s primary garbage collection mechanism is **reference counting**. Every object has a count of references pointing to it. When this count drops to zero, the object’s memory is immediately deallocated.

However, reference counting alone cannot handle **reference cycles** (e.g., two objects that refer to each other). To solve this, Python has a supplemental **generational garbage collector** that periodically runs to detect and clean up these cycles.

3. What are descriptors and how are they used?

A descriptor is an object attribute with “binding behavior,” whose attribute access has been overridden by methods in the descriptor protocol: `__get__()`, `__set__()`, and `__delete__()`. If any of those methods are defined for an object, it is said to be a descriptor.

They are the underlying mechanism behind properties, methods, `staticmethod`, `classmethod`, and `super()`. They allow for reusable logic that can customize attribute access, making them powerful for building frameworks and ORMs.

Read the official Descriptor HOWTO Guide.

4. Explain what metaclasses are and provide a practical use case.

A metaclass is the “class of a class.” Just as a class defines how an instance behaves, a metaclass defines how a class behaves. The default metaclass is `type`.

A practical use case is creating a plugin framework or an ORM. For example, a metaclass could automatically register any new model class with a central registry or enforce that all subclasses must have a certain attribute defined.

5. What is the difference between a generator expression and a list comprehension?

Both create iterators, but they differ in how they handle memory and execution.

  • A **list comprehension** (`[i for i in range(10)]`) creates the entire list in memory at once.
  • A **generator expression** (`(i for i in range(10))`) creates a generator object. It produces items one by one as they are requested and does not store the whole list in memory.

Generator expressions are more memory-efficient and should be used for very large sequences.

6. How do Python’s decorators work with arguments?

A decorator with arguments is essentially a decorator factory. It’s a higher-order function that returns a decorator. The structure is a function nested three levels deep:

  1. The outer function takes the decorator’s arguments (e.g., `role`).
  2. The middle function is the actual decorator, which takes the function to be decorated.
  3. The inner function is the wrapper that executes the decorated function’s logic.

7. What are context managers and the `with` statement?

A context manager is an object that defines the methods `__enter__()` and `__exit__()`, used to set up and tear down a temporary context. The `with` statement ensures that the `__exit__()` method is always called, even if exceptions occur. This is crucial for managing resources like file handles, database connections, or locks, guaranteeing they are properly released.

Async & Concurrency

8. Compare `threading`, `multiprocessing`, and `asyncio`. When would you use each?

  • `threading`: Uses threads within the same process. Best for **I/O-bound** tasks due to the GIL. Shares memory, but requires locks for thread safety.
  • `multiprocessing`: Spawns separate processes, each with its own interpreter and memory space. It bypasses the GIL, making it ideal for **CPU-bound** tasks. Communication requires IPC (e.g., pipes, queues).
  • `asyncio`: Uses a single-threaded, single-process cooperative multitasking model. It’s perfect for a very high number of **I/O-bound** tasks (e.g., tens of thousands of network connections) with lower overhead than threading.
Read the official `asyncio` documentation.

9. Explain the `asyncio` event loop.

The `asyncio` event loop is the core of every asyncio application. It’s a scheduler that runs asynchronous tasks and callbacks, performs network I/O, and runs subprocesses. When an `async` function uses `await` on a task (like a network call), it yields control back to the event loop, which can then run other tasks until the awaited operation is complete.

10. What is the difference between `async def` and `def` functions?

A function defined with `def` is a regular synchronous function. When called, it runs to completion before returning.

A function defined with `async def` creates a **coroutine**. When called, it doesn’t execute immediately but returns a coroutine object. This object must be scheduled on an event loop (e.g., using `asyncio.run()` or `await`) to run.

11. What problem does `async/await` solve compared to older callback-based async patterns?

`async/await` provides syntactic sugar that makes asynchronous code look and behave more like synchronous code. This solves the problem of “callback hell” or “pyramid of doom,” where nested callbacks make code difficult to read, reason about, and maintain. Error handling is also simplified, as you can use standard `try…except` blocks instead of passing error arguments to callbacks.

Web Frameworks & APIs

12. Explain the request/response cycle in Django.

  1. A user makes a request to a URL.
  2. The request hits the WSGI/ASGI server (e.g., Gunicorn).
  3. Django’s middleware stack processes the incoming request.
  4. The URL router (`urls.py`) matches the URL to a view function.
  5. The view function processes the request, interacts with models (database), and prepares a response.
  6. The view can render a template to generate HTML.
  7. The response travels back through the middleware stack.
  8. The server sends the final HTTP response to the client.

13. How would you optimize database queries in Django ORM?

  • `select_related`: For pre-fetching one-to-one or foreign key relationships in a single SQL query (using JOINs).
  • `prefetch_related`: For many-to-many or reverse foreign key relationships. It performs a separate lookup for the related objects and joins them in Python, avoiding massive JOINs.
  • `values()` / `values_list()`: To retrieve only the specific columns you need as dictionaries or tuples, instead of full model objects.
  • `defer()` / `only()`: To defer loading of specific fields or load only specific fields.
  • `QuerySet.explain()`: To analyze the database query plan.
See the Django docs on database optimization.

14. What are class-based views vs. function-based views in Django/Flask?

Function-based views (FBVs) are simple Python functions that take a request and return a response. They are explicit and easy to understand.

Class-based views (CBVs) use classes to structure views. This allows for inheritance and mixins, promoting code reuse for common patterns (e.g., displaying a list of objects or handling form submissions). They are great for building scalable and maintainable code but have a steeper learning curve.

15. How do you handle authentication and authorization in a REST API?

Authentication (who are you?) is typically handled using tokens. A common method is JWT (JSON Web Tokens). The user logs in with credentials, receives a signed JWT, and includes it in the `Authorization` header of subsequent requests.

Authorization (what can you do?) is handled after authentication. It involves checking the user’s roles or permissions against the action they are trying to perform. This logic can be implemented in middleware or decorators.

16. Explain the difference between WSGI and ASGI.

WSGI (Web Server Gateway Interface) is the standard interface between Python web applications and web servers for synchronous code. It was designed for a simple request/response cycle.

ASGI (Asynchronous Server Gateway Interface) is the successor to WSGI, designed to support asynchronous Python frameworks. It can handle long-lived connections (like WebSockets) and other protocols beyond the simple request/response model, making it essential for modern `asyncio`-based applications.

Read the ASGI documentation.

17. What are some best practices for designing a RESTful API?

  • Use nouns for resource URLs (e.g., `/users`, `/users/123`).
  • Use HTTP verbs correctly (GET, POST, PUT, PATCH, DELETE).
  • Use HTTP status codes appropriately (2xx for success, 4xx for client errors, 5xx for server errors).
  • Support versioning (e.g., `/api/v1/users`).
  • Provide clear and consistent error messages.
  • Use pagination for large collections.

18. What is middleware in a web framework?

Middleware is a component that processes requests and responses globally before they reach the view and after they leave the view. It forms a chain or “stack” through which data flows. Common use cases include user authentication, logging, CSRF protection, and adding custom headers to every response.

Architecture & System Design

19. How would you design a caching strategy for a high-traffic web application?

A multi-level caching strategy is often best:

  1. Client-Side/Browser Cache: Use HTTP headers like `ETag` and `Cache-Control`.
  2. CDN Cache: Cache static assets and public API responses geographically close to users.
  3. Reverse Proxy/Load Balancer Cache: Cache common responses before they hit the application server.
  4. In-Memory Application Cache: Use a distributed cache like **Redis** or **Memcached** to store frequently accessed data, such as query results or user sessions.
  5. Database Cache: The database itself often has internal caching mechanisms.
Learn about caching with Redis.

20. What is Celery and what problem does it solve?

Celery is a distributed task queue for Python. It allows you to run time-consuming or periodic tasks asynchronously in the background, outside of the main application’s request/response cycle. This is essential for tasks like sending emails, processing images, or running nightly reports without blocking the web server and degrading user experience.

It requires a message broker (like RabbitMQ or Redis) to handle communication between the application and the Celery workers.

Get started with Celery.

21. Explain the SOLID principles of object-oriented design.

  • Single Responsibility Principle: A class should have only one reason to change.
  • Open/Closed Principle: Software entities should be open for extension, but closed for modification.
  • Liskov Substitution Principle: Subtypes must be substitutable for their base types without altering the correctness of the program.
  • Interface Segregation Principle: Clients should not be forced to depend on interfaces they do not use.
  • Dependency Inversion Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions.

22. What are the trade-offs of using a microservices architecture vs. a monolith?

Monolith Pros: Simple to develop, test, and deploy initially. No network latency between components.

Monolith Cons: Becomes hard to maintain as it grows, scaling is all-or-nothing, a bug can take down the entire application, technology stack is fixed.

Microservices Pros: Services can be scaled, deployed, and maintained independently. Teams can work autonomously. Technology stack can be diverse. Better fault isolation.

Microservices Cons: Higher operational complexity (DevOps), network latency, challenges with distributed data management and transactions.

23. How would you handle database schema migrations in a production environment?

Use a dedicated migration tool like **Alembic** (for SQLAlchemy) or Django’s built-in migrations. The key principles are:

  • Migrations should be version-controlled with the codebase.
  • They must be backward-compatible to allow for zero-downtime deployments. For example, when renaming a column, you would first add the new column, deploy code to write to both, backfill data, deploy code to read from the new column, and finally drop the old column in a later migration.
  • Always test migrations in a staging environment that mirrors production.

24. What are some strategies for securing a backend application?

  • Dependency Management: Regularly scan for vulnerabilities using tools like `pip-audit` or Snyk.
  • Input Validation: Never trust user input. Validate and sanitize all data to prevent injection attacks (SQLi, XSS).
  • Principle of Least Privilege: The application’s database user should only have the permissions it absolutely needs.
  • Rate Limiting: Protect against brute-force attacks.
  • Secrets Management: Use environment variables and a secure vault (like HashiCorp Vault or AWS Secrets Manager) instead of hardcoding credentials.

Testing & Tooling

25. Explain the difference between mocking and stubbing.

Both are forms of test doubles, but they have different purposes.

A **stub** provides canned answers to calls made during the test. It’s used when you want to control the behavior of a dependency to test a specific path in your code (e.g., make a network call always return a specific error).

A **mock** is an object on which you set expectations about how it will be called. After the test runs, you can verify if the mock was called with the correct arguments and the right number of times. Mocks are used to test the interaction between objects.

26. What is `pytest` and what are some of its key features over the standard `unittest` module?

`pytest` is a popular third-party testing framework for Python. Key features include:

  • Less Boilerplate: Tests are simple functions, not classes that must inherit from `unittest.TestCase`.
  • Fixtures: A powerful dependency injection mechanism for setting up and tearing down test state.
  • Powerful Assertions: Uses the standard `assert` statement and provides detailed introspection on failures.
  • Rich Plugin Ecosystem: Extensive support for plugins like `pytest-cov` (coverage) and `pytest-django`.

27. How would you profile a Python application to find performance bottlenecks?

I would use Python’s built-in `cProfile` module to identify functions that are taking the most CPU time. For memory issues, `memory-profiler` can provide a line-by-line analysis of memory consumption. For web applications, tools like `py-spy` can profile a running process without modifying its code, and APM (Application Performance Monitoring) services like Datadog or New Relic provide a holistic view in production.

28. What is the purpose of a `.pyi` file?

A `.pyi` file is a **stub file** used by static type checkers like Mypy. It contains type hints for a corresponding `.py` module but no implementation code. They are used to add type information to third-party libraries that don’t include it, or to separate type hints from the implementation for cleaner code.

29. What is containerization (e.g., Docker) and why is it useful for backend development?

Containerization is a method of packaging an application and all its dependencies (libraries, system tools, code, runtime) into a single, isolated unit called a container. It’s useful because:

  • Consistency: It ensures the application runs the same way everywhere, from a developer’s laptop to production.
  • Isolation: Containers run in isolation, preventing conflicts between different applications on the same host.
  • Scalability: It’s easy to spin up and tear down container instances, making it a cornerstone of modern microservices and cloud-native applications.

30. What is the difference between a virtual environment and a container?

A **virtual environment** (like `venv`) isolates Python package dependencies for a specific project. It creates a separate directory of Python packages but still uses the host machine’s operating system and Python interpreter.

A **container** (like Docker) isolates the entire application environment, including the operating system’s user space, system libraries, and the application runtime itself. It provides a much higher level of isolation than a virtual environment.

Remote hiring made easy

75%
faster to hire
58%
cost savings
800+
hires made
Explore More