460 Python Developer Interview Questions & Answers

154 top • 31 Amazon • 49 Google • 44 Netflix • 48 Meta • 41 NVIDIA • 47 Apple • 46 Microsoft

Python Developer icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: September 3, 2026)

111. How would you use pytest parametrization to test many inputs without duplicating test code?TestingMedium

Question Details

Explain @pytest.mark.parametrize, multiple parameters, readable case identifiers, expected exceptions, edge cases, and when separate tests are clearer than one large parameterized test.

Short Interview Answer (30-60 seconds)

I use pytest.mark.parametrize when the same behavior should be checked with several inputs and expected results. I keep one clear test body, pass multiple parameters when the case needs them, and give important cases readable ids. For expected exceptions, I include the expected exception in the case data and use pytest.raises only for those cases. The tradeoff is that a large parameter table can hide intent, so I use separate tests when cases need different setup, assertions, or business explanations.

Detailed Explanation

See the Code while reading this explanation.

The practical decision is to parametrize only cases that follow the same test flow. The unit under test stays the same, the setup is similar, the action is the same, and the assertion shape is the same. Only the inputs and expected outcomes change.

Useful Questions to Ask the Interviewer
  1. What behavior and test boundary should I cover?
  2. Which dependencies, environments, and test tools should I assume?
  3. Which failures, edge cases, and quality risks are most important?

With pytest.mark.parametrize, I define parameter names and provide a list of cases. Pytest creates one independent test case for each row. This removes repeated test functions while still reporting each case separately. For example, a discount function can be tested with an amount, a customer type, and an expected total. The test body calls the function once and compares the result with the expected value.

Multiple parameters are useful when the behavior depends on more than one value. The parameter names should describe the business meaning, such as amount, customer_type, and expected_total. I avoid vague names such as a, b, and result because they make failures harder to understand.

Readable case identifiers help when the raw parameter values are not clear. I can use ids with short labels such as regular_customer, vip_customer, zero_amount, and boundary_amount. Another option is pytest.param with id for each case. The id should explain why the case exists, not repeat every input value.

Expected exceptions need a clear pattern. If only a few cases should fail, I can store the expected exception type with each case. The test uses pytest.raises for exception cases and a normal equality assertion for success cases. I keep success and failure handling explicit so the reader can see the intended behavior. When success and exception cases need very different setup or assertions, separate tests are usually clearer than one complicated parameterized test.

Edge cases should come from the real contract of the function. Typical examples include zero, empty input, minimum and maximum allowed values, invalid types when the function validates them, and values directly around a business boundary. I do not add random cases without a reason. Each row should represent a distinct behavior or risk.

Parametrized cases should remain deterministic and independent. The test should not mutate shared input objects or depend on execution order. If a case uses a list, dictionary, or custom object that the function may modify, I create fresh data for each case with a factory or fixture. Pytest reports each parameter set separately in local runs and CI, which makes failures easier to locate.

A large parameterized test becomes misleading when rows test different behaviors, require many optional fields, or use branching logic to choose different assertions. At that point, I split the cases into smaller parameterized tests or separate named tests. Parametrization should reduce duplication without hiding the purpose of the test.

How would you use pytest parametrization to test many inputs without duplicating test code? diagram
Key Insight / Why This Solution Works
  1. Identify one behavior that should work for several inputs.
  2. Confirm that every case uses the same setup, action, and assertion shape.
  3. Choose clear parameter names.
  4. Create cases with inputs and expected outcomes.
  5. Add readable ids for important or non obvious cases.
  6. Use pytest.raises for cases that expect an exception.
  7. Add boundary and edge cases from the real function contract.
  8. Keep each case independent and deterministic.
  9. Split the test when cases require different setup, actions, or assertions.
  10. Run the cases in CI and use the case ids to diagnose failures.
Example

The example tests a calculate_shipping function with several order totals and customer types. The first test uses multiple parameters and readable ids for successful cases. The second test parametrizes invalid inputs and verifies the expected exception with pytest.raises. Each case follows one clear test flow, and success and exception cases are separated because their assertions are different.

Code
import pytest


def calculate_shipping(order_total: int, customer_type: str) -> int:
    if order_total < 0:
        raise ValueError("order_total must be zero or greater")
    if customer_type not in {"regular", "vip"}:
        raise ValueError("unsupported customer type")
    if customer_type == "vip" or order_total >= 5000:
        return 0
    return 500


@pytest.mark.parametrize(
    ("order_total", "customer_type", "expected_shipping"),
    [
        pytest.param(0, "regular", 500, id="zero-total"),
        pytest.param(4999, "regular", 500, id="below-free-shipping"),
        pytest.param(5000, "regular", 0, id="free-shipping-boundary"),
        pytest.param(1000, "vip", 0, id="vip-customer"),
    ],
)
def test_calculate_shipping(
    order_total: int,
    customer_type: str,
    expected_shipping: int,
) -> None:
    result = calculate_shipping(order_total, customer_type)
    assert result == expected_shipping


@pytest.mark.parametrize(
    ("order_total", "customer_type", "expected_message"),
    [
        pytest.param(
            -1,
            "regular",
            "order_total must be zero or greater",
            id="negative-total",
        ),
        pytest.param(
            1000,
            "unknown",
            "unsupported customer type",
            id="unsupported-customer-type",
        ),
    ],
)
def test_calculate_shipping_rejects_invalid_input(
    order_total: int,
    customer_type: str,
    expected_message: str,
) -> None:
    with pytest.raises(ValueError, match=expected_message):
        calculate_shipping(order_total, customer_type)
Where it is used

Pytest parametrization is used for validation rules, parsing functions, pricing logic, permission checks, date calculations, API input validation, and algorithms with many boundary cases. It is especially useful when the same public behavior must be checked against a table of inputs and expected outputs.

Why Interviewers Ask This

Interviewers ask this to see whether the candidate can organize many input cases without copying the same test body. They are evaluating knowledge of pytest parametrization, readable case design, exception testing, edge case coverage, and the judgment to split tests when one parameter table becomes difficult to understand.

Common interview mistakes

A common mistake is placing unrelated behaviors in one parameter table. Another is adding branching logic inside the test so every row follows a different path. Other mistakes include unclear parameter names, missing case ids, sharing mutable data between cases, repeating the same case with different values but no new behavior, mixing success and exception assertions in a confusing way, and creating a very large table that is harder to understand than several focused tests.

Interview tip

Explain that parametrization is best when the setup, action, and assertion shape stay the same. Mention multiple parameters, readable ids, explicit exception cases, real edge cases, and the point where separate tests become clearer.

Interviewer may ask next
How would you parametrize cases that expect different exception types?

I would include the expected exception type and message in each failure case, then pass them to pytest.raises. The boundary remains one failure behavior with the same setup and action. This matters because each row stays explicit and pytest reports the failing case by id. If the exception cases need different setup or very different assertions, I would split them into separate tests.

When would you stop adding rows and create separate tests?

I would create separate tests when cases no longer share the same setup, action, and assertion shape. The boundary changes from one repeated behavior to several distinct behaviors. This matters because branches, optional parameters, and unrelated expectations make a parameter table hard to read. The tradeoff is a small amount of repeated structure in exchange for clearer intent and easier failure diagnosis.

112. How would you mock external dependencies in Python tests without making the tests misleading?TestingHard

Question Details

Explain unittest.mock or monkeypatch, where to patch an imported dependency, how to mock HTTP clients, clocks, queues, and databases, and how over-mocking can hide integration problems.

Short Interview Answer (30-60 seconds)

I mock only the dependency that is outside the unit test boundary, and I patch the name where the code under test looks it up. For HTTP clients, clocks, queues, and database gateways, I return controlled results and assert only important calls and visible behavior. I keep the fake response close to the real contract, cover errors and timeouts, and add separate integration or contract tests with real components. The main tradeoff is speed and isolation versus confidence in the real integration, so I avoid mocking every internal method.

Detailed Explanation

See the Code while reading this explanation.

The first decision is the test boundary. In a unit test, I keep the business function real and replace only dependencies that leave that boundary, such as an HTTP client, the current clock, a queue publisher, or a database gateway. The unit test should prove the behavior of the business function, not prove that the external service or database works.

Useful Questions to Ask the Interviewer
  1. What behavior and test boundary should I cover?
  2. Which dependencies, environments, and test tools should I assume?
  3. Which failures, edge cases, and quality risks are most important?

I patch where the code under test looks up the dependency. Suppose service.py contains from payments import charge and then calls charge. The test should patch service.charge because that is the name used by service.py. Patching payments.charge may leave the already imported name unchanged, which can cause the test to call the real dependency or fail for the wrong reason.

For HTTP calls, I normally replace the HTTP client object or the small client wrapper used by the application. The test returns a realistic response object with the status and body shape that the application actually reads. I test success, timeout, connection failure, invalid data, and relevant error responses. I also assert the important request contract, such as the URL, payload, headers, and timeout, but I avoid asserting every private helper call.

For clocks, I replace the clock function or inject a clock object so the test uses a fixed instant. This keeps expiry, retry, and scheduling tests deterministic. For queues, I replace the publisher at the application boundary and assert the message topic and payload. A lightweight fake queue can be better when several operations must work together because it behaves more like the real interface. For databases, I may mock a repository in a small service unit test, but I use a real controlled database for queries, constraints, transactions, mappings, and migrations. Mocking a database cannot prove that SQL or schema behavior is correct.

The setup should be small and explicit. A pytest fixture can create the mock and restore the original attribute automatically after the test. The test arranges controlled dependency results, runs one public action, and asserts the returned value, state change, or raised error. It may also verify one important interaction, such as publishing one message, but it should not mirror the complete internal call sequence.

Over mocking makes tests misleading when every collaborator is replaced, return values are invented without matching the real contract, or assertions depend on private implementation details. Such tests can pass even when the real API changed, the queue rejects the payload, or the database constraint fails. To prevent this, I keep unit tests focused, add contract tests for external request and response shapes, add integration tests for selected real components, and run those tests in CI. Mocked unit tests provide fast feedback, while integration and contract tests provide confidence that the boundaries still work.

Cleanup should be automatic. unittest.mock.patch, pytest monkeypatch, and scoped fixtures restore replaced names after each test. Tests should not depend on order, shared mutable mocks, live network access, production queues, or production databases. This keeps the suite repeatable on a developer machine and in CI.

How would you mock external dependencies in Python tests without making the tests misleading? diagram
Key Insight / Why This Solution Works
  1. Define the public behavior being tested and name the exact unit boundary.
  2. List dependencies that leave that boundary, such as the HTTP client, clock, queue publisher, or repository.
  3. Patch each dependency where the code under test looks it up.
  4. Configure realistic controlled results for success, failure, and edge cases.
  5. Run the public function or method once.
  6. Assert the visible result, state change, or error.
  7. Verify only important interactions and contracts.
  8. Let the fixture or patch context restore the original dependency.
  9. Add contract or integration tests for behavior that mocks cannot prove.
  10. Run unit, contract, and integration tests at suitable stages in CI.
Example

The example tests a checkout service. The service uses a payment client, a queue publisher, and a clock that are imported into the service module. The test patches those names in the service module because that is where the code looks them up. It fixes the time, returns a realistic payment result, runs checkout, asserts the returned order data, and verifies the important payment and queue contracts. A second test makes the payment client raise a timeout and confirms that no queue message is published. The patches are scoped to each test and are restored automatically.

Code
from datetime import datetime, timezone
from unittest.mock import Mock, patch

import pytest

import checkout_service


def test_checkout_charges_customer_and_publishes_event():
    fixed_time = datetime(2026, 7, 20, 12, 0, tzinfo=timezone.utc)
    payment_result = {"payment_id": "pay_123", "status": "approved"}

    with (
        patch("checkout_service.utc_now", return_value=fixed_time),
        patch("checkout_service.payment_client.charge", return_value=payment_result) as charge_mock,
        patch("checkout_service.event_publisher.publish") as publish_mock,
    ):
        result = checkout_service.checkout(
            order_id="order_100",
            customer_id="customer_7",
            amount_cents=2500,
        )

    assert result == {
        "order_id": "order_100",
        "payment_id": "pay_123",
        "status": "paid",
        "paid_at": fixed_time,
    }
    charge_mock.assert_called_once_with(
        customer_id="customer_7",
        amount_cents=2500,
        idempotency_key="order_100",
    )
    publish_mock.assert_called_once_with(
        "order.paid",
        {
            "order_id": "order_100",
            "payment_id": "pay_123",
            "paid_at": fixed_time.isoformat(),
        },
    )


def test_checkout_does_not_publish_when_payment_times_out():
    with (
        patch(
            "checkout_service.payment_client.charge",
            side_effect=TimeoutError("payment provider timeout"),
        ),
        patch("checkout_service.event_publisher.publish") as publish_mock,
    ):
        with pytest.raises(TimeoutError, match="payment provider timeout"):
            checkout_service.checkout(
                order_id="order_100",
                customer_id="customer_7",
                amount_cents=2500,
            )

    publish_mock.assert_not_called()
Where it is used

This approach is used when a Python service calls payment providers, email services, cloud APIs, message brokers, clocks, or repositories. For example, an order service unit test can replace the payment client and queue publisher while keeping order rules real. Separate contract tests can verify the payment request shape, and integration tests can verify the real database transaction and queue adapter.

Why Interviewers Ask This

Interviewers ask this to see whether the candidate can isolate a small unit of Python code while still preserving confidence in the real system. They are evaluating whether the candidate knows where to patch, how to choose between mocks, stubs, and fakes, how to make tests deterministic, and when a real integration test is required because a mocked test cannot prove that an external contract still works.

Common interview mistakes

A common mistake is patching the module that originally defined a function instead of the name used by the module under test. Another mistake is inventing mock responses that do not match the real external contract. Tests also become fragile when they assert every internal call, call order, or private method. Over mocking can make the test pass while the real HTTP API, queue, or database integration is broken. Other mistakes include sharing mutable mocks between tests, allowing accidental live network calls, using fixed sleep calls, ignoring timeout and failure paths, mocking database behavior that should be checked against a real controlled database, and treating unit test coverage as proof that integrations work.

Interview tip

Start by naming the unit boundary. Then say that you patch at the lookup location, keep mock data faithful to the real contract, assert visible behavior, and use separate contract or integration tests for confidence that the real dependency works.

Interviewer may ask next
How would you stop a mocked HTTP test from passing after the provider changes its response?

I would keep the unit test boundary around the application logic, but add a contract test for the HTTP client wrapper. The contract test would validate the real or provider supplied response shape that the wrapper expects, including required fields and error forms. The unit mock would reuse a fixture based on that contract. This matters because a unit mock cannot detect an external schema change. The tradeoff is that contract tests are slower and may require a sandbox or recorded provider response, but they protect against misleading mock data.

When would you replace a database mock with a real test database?

I would change the boundary when the behavior depends on real queries, constraints, transactions, mappings, or migrations. The service unit test can still mock the repository, but the repository itself should be tested against a controlled test database with migrations and isolated cleanup. This matters because a mock cannot reproduce database rules reliably. The tradeoff is longer setup and CI runtime in exchange for confidence in actual database behavior.

113. What is a REST API?API DesignEasy

Question Details

Define a REST API in practical HTTP terms. Explain resources and URLs, HTTP methods, representations such as JSON, status codes, stateless requests, validation, consistent errors, authentication, pagination, idempotency, and caching. Use one small Python service example and distinguish REST from a Python framework or a transport protocol.

Short Interview Answer (30-60 seconds)

At a high level, a REST API lets applications work with resources through standard HTTP rules. In this design, a client calls a Python REST API service using URLs like /users and /users/{id}. GET reads users, POST creates one, PUT updates one, and DELETE removes one. JSON carries resource data, while HTTP status codes explain the result. Each request is stateless and can carry a Bearer token for authentication. Pagination and caching improve efficiency. The trade-off is more API design work in exchange for a predictable interface.

Detailed Explanation

This question asks how two programs can communicate in a clear and predictable way. The client needs simple ways to list users, get one user, create one, update one, or delete one. The service must also clearly say whether each request worked or failed. It should protect requests, reject bad input, and handle large result lists efficiently. The main challenge is keeping these rules consistent for every client. I would explain the same user API shown in the diagram and connect each REST idea to that practical example.

Useful Questions to Ask the Interviewer
  • Are we discussing a simple public HTTP API or an internal service API?
  • Should I focus only on REST basics, or also explain security and performance?
  • Is the user resource shown in the diagram enough for the example?
What is a REST API? diagram
How to Explain It in an Interview
1. Start with resources and URLs

I would start by saying that REST is an architectural style for web APIs. A resource is a thing the API manages, such as a user. Each resource has a URL that identifies where clients work with it. The diagram uses /users for the user collection. It uses /users/{id} for one specific user. Good REST URLs normally use resource names rather than action verbs. REST is not a Python framework. It is also not a transport protocol like HTTP. A Python framework can implement REST rules, while HTTP carries the requests and responses.

2. Use HTTP methods for actions

The request uses an HTTP method to say what action is wanted. GET /users lists users. GET /users/{id} gets one user. POST /users creates a new user. PUT /users/{id} updates one user. DELETE /users/{id} removes one user. This keeps the URLs focused on resources. The client sends these HTTP requests through the network to the Python REST API service. The service processes the request and sends an HTTP response back to the client.

3. Send clear request and response data

JSON is the main representation shown in the diagram. A representation is the data format used to describe a resource. For example, POST /users sends Content-Type: application/json with {"name":"Asha","email":"a@x.com"}. A successful create returns 201 Created with the new user data. A successful read returns 200 OK. The update example also returns 200 OK. A successful delete returns 204 No Content. These status codes tell the client what happened without making it guess.

4. Keep requests stateless and validate input

Each request should contain everything needed to handle that request. This is called statelessness. The server does not depend on hidden client state from an earlier request. The service should also validate incoming data before processing it. Invalid client input uses 400 Bad Request. A missing resource uses 404 Not Found. An unexpected server problem uses 500 Internal Server Error. The diagram also shows a consistent JSON error shape with error, message, and status fields. A consistent format makes failures easier for clients to handle.

5. Authenticate protected requests

The diagram shows requests carrying Authorization: Bearer <token>. A Bearer token is a credential the client sends in an HTTP header. The API uses authentication to check whether the caller has valid credentials. The diagram uses 401 Unauthorized when authentication is required. HTTPS is also shown as part of securing the API because it protects data while it travels across the network. Authentication is separate from the REST resource model. It protects access while the same resource URLs and HTTP methods remain unchanged.

6. Handle large lists, repeated calls, and caching

For a large user collection, the diagram uses pagination. The client can call GET /users?page=1&limit=10. The response can include a next link such as /users?page=2. This avoids returning every user in one response. The diagram also treats GET, PUT, and DELETE as idempotent. Idempotent means repeating the same operation should not create extra changes. POST is not shown as idempotent. For repeated reads, caching headers can reduce server work. The diagram specifically shows Cache-Control, ETag, and Last-Modified as caching mechanisms.

7. Close with the practical REST idea

The main idea is consistency. Clients know which URLs represent resources, which HTTP methods perform actions, and which status codes describe results. They also receive predictable JSON data and errors. The Python REST API service is one implementation of these rules. REST itself is not the Python framework and is not HTTP itself. The benefit is an API that is easier for different clients to understand. The downside is that the team must carefully define URLs, methods, validation, authentication, errors, pagination, idempotency, and caching behavior.

Practical Complexity & Trade-offs

The benefit of this design is consistency. The same resource URLs and HTTP methods are easy for clients to learn. Status codes and one error format make failures easier to handle. Stateless requests keep each call independent. Pagination reduces how much data a large list returns at once. Caching can reduce repeated server work and improve response speed. Authentication and HTTPS protect access and network traffic. The downside is extra design work. The team must define validation, page behavior, cache rules, and error responses carefully. Idempotency also matters when clients repeat requests after network problems. GET, PUT, and DELETE can follow idempotent behavior, while POST can create another resource when repeated. We accept this work because clear rules make the API easier to use and maintain.

Why Interviewers Ask This

Interviewers ask this to check whether you understand REST as practical API design, not just as a definition. They want to see clear resource URLs, correct HTTP methods, status codes, and request-response behavior. They also look for statelessness, validation, consistent errors, authentication, pagination, idempotency, and caching. A strong answer separates REST from a Python framework and from HTTP itself. The key skill is explaining why each choice helps clients and servers communicate predictably.

Interviewer may ask next
How would this REST API handle a very large number of users?

I would keep the same /users resource and change how GET /users returns the collection. The diagram already shows pagination with page and limit query parameters. For example, the client can call GET /users?page=1&limit=10. The response returns only that page and can include a next link such as /users?page=2. This keeps each response smaller and avoids sending every user at once. For repeated reads, I would also use the caching mechanisms shown in the diagram. Cache-Control, ETag, and Last-Modified can help avoid unnecessary data transfers when content has not changed. The resource URLs, HTTP methods, Bearer-token authentication, validation, and status-code rules stay the same. The main downside is more client logic. Clients must follow page links and correctly handle cache behavior. We accept that complexity because pagination and caching make large read operations more efficient.

What happens if clients repeat requests or send invalid authentication?

I would keep the behavior predictable by following the idempotency and authentication rules shown in the diagram. GET, PUT, and DELETE are treated as idempotent operations. This means repeating the same operation should not create extra changes. For example, repeating PUT /users/{id} should leave the user in the same requested state. Repeating DELETE /users/{id} should not delete another resource. POST is different because repeating POST /users can create another user. For protected calls, the client sends Authorization: Bearer <token>. The Python REST API service uses authentication to check the credential. The diagram uses 401 Unauthorized when authentication is required. Invalid client data uses 400 Bad Request, while a missing user uses 404 Not Found. The downside is that clients must understand these different outcomes. The benefit is clear and consistent behavior when requests fail or are repeated.

114. What is FastAPI?API DesignEasy

Question Details

Define FastAPI as a Python framework for building web APIs using standard type hints. Explain path operations, request parsing, validation, response models, dependency injection, automatic OpenAPI documentation, async endpoint support, ASGI serving, exception handling, and the distinction between FastAPI, an ASGI server such as Uvicorn, and the application business logic.

Short Interview Answer (30-60 seconds)

At a high level, FastAPI is a Python framework for building web APIs with standard Python type hints. A client sends a request to FastAPI. FastAPI parses and validates the data, resolves dependencies, runs the path operation, validates the response model, and sends JSON back. It also creates OpenAPI documentation, supports async endpoints, and handles API exceptions. Uvicorn is the ASGI server that runs the FastAPI application, while my code contains the business logic. The main trade-off is learning framework concepts in return for clear validation, reusable dependencies, and less repeated API code.

Detailed Explanation

This question asks what FastAPI is and what work it does for a web API. The goal is to build an API without manually handling every common task. A client sends some data and expects a clear response. FastAPI helps check that data, call the correct function, and prepare the result. The main challenge is understanding which work belongs to FastAPI, which work belongs to the server, and which work belongs to our own code. I would explain the same request flow shown in the diagram, from client request to JSON response.

Useful Questions to Ask the Interviewer
  • Do you want only a FastAPI definition, or also the full request flow?
  • Should I explain the difference between FastAPI and Uvicorn?
  • Would you like me to walk through the example POST /items/ path operation?
What is FastAPI? diagram
How to Explain It in an Interview
1. Start with what FastAPI provides

I would start by saying FastAPI is a Python framework for building web APIs. It uses standard Python type hints to understand expected data types. Those type hints help FastAPI parse input, validate values, shape responses, and generate documentation. This reduces repeated API code and makes the API contract easier to understand. FastAPI creates an ASGI application object, but it does not replace the server that accepts network connections.

2. Follow the request from the client

The request first comes from a browser or another API client. It reaches the FastAPI application and matches a path operation. A path operation is a Python function connected to an HTTP method and path. The diagram shows POST /items/ connected to create_item. FastAPI can read path values, query values, headers, and request bodies. It then checks them against Python type hints and Pydantic models before the endpoint function runs.

3. Resolve dependencies and run the path operation

Before calling the endpoint, FastAPI can resolve reusable dependencies with Depends(). The example uses Depends(get_token). The diagram shows dependencies being useful for database sessions, authentication, services, and settings. After those dependencies are available, FastAPI runs the path operation. The endpoint then performs the application work. That business logic belongs to our code. It may include services, database operations, authentication, authorization, or other application rules shown in the diagram.

4. Handle errors and create the response

FastAPI provides exception handling for expected API errors. The example checks whether the token equals valid-token. If it does not, the endpoint raises HTTPException with status code 401 and detail Unauthorized. When the operation succeeds, it returns an Item. The path operation declares response_model=Item. FastAPI uses that response model to validate and shape the returned data. It then serializes the response to JSON and sends it back to the client.

5. Explain automatic documentation and async endpoints

FastAPI creates an OpenAPI schema from the application code. The diagram also shows interactive API documentation at /docs. This is useful because the documentation comes from the same routes and models used by the application. FastAPI also supports async def and await. Async endpoints are helpful when code spends time waiting for input-output work. While one request waits, the server can continue working with other connections.

6. Separate FastAPI, Uvicorn, and business logic

I would finish by separating the three responsibilities clearly. FastAPI is the framework. It defines routes and models, parses and validates data, injects dependencies, creates OpenAPI documentation, and handles exceptions. Uvicorn is an ASGI server. It runs the FastAPI application, handles network connections, and manages concurrency and workers. The diagram runs the app with uvicorn main:app --reload. Business logic is our own code. It contains endpoint behavior, services, database operations, authentication, authorization, and other application rules. So the client sends a request, FastAPI processes it, our code performs the work, and FastAPI returns the JSON response.

Practical Complexity & Trade-offs

The benefit of FastAPI is that many common API tasks are built into one framework. Type hints and Pydantic models make request and response rules clear. Dependency injection makes shared code easier to reuse and test. Automatic OpenAPI documentation reduces manual documentation work. Async support helps when endpoints spend time waiting on input-output operations. The downside is that developers must learn FastAPI concepts such as path operations, response models, dependencies, and ASGI. Uvicorn is also a separate server component that must be understood and operated. Validation adds useful checks, but it also adds some processing. We accept this because it gives clearer API contracts, fewer manual checks, reusable components, and easier maintenance.

Why Interviewers Ask This

Interviewers ask this to see whether you understand FastAPI beyond a simple definition. They want to hear how path operations, request validation, response models, dependencies, async endpoints, documentation, and exception handling fit together. They also check whether you separate responsibilities correctly. FastAPI defines and processes the API. Uvicorn runs the ASGI application. Your code owns the business logic. A strong answer shows clear API boundaries, correct request and response flow, and sensible framework trade-offs.

Interviewer may ask next
What changes if this FastAPI application must handle many slow network operations at the same time?

I would keep the same overall FastAPI design, but I would use async path operations where the code spends time waiting. The affected part is the endpoint function and the business logic it calls. I would define those operations with async def and use await for supported input-output work. FastAPI would still parse and validate the request first. It would still resolve dependencies before running the path operation. The response model would still validate and shape the returned data. Uvicorn would still run the ASGI application and manage concurrent connections. The main benefit is that one worker can continue handling other connections while one request waits. This can improve concurrency for network-heavy work. The downside is that async code adds another programming model to understand. It also does not make CPU-heavy work faster by itself. I would therefore use async where waiting is the real bottleneck, while keeping the rest of the design unchanged.

How would you explain the difference between FastAPI, Uvicorn, and the application business logic?

I would separate them by responsibility. FastAPI is the framework that defines path operations and handles API-level work. It parses request data, validates types and Pydantic models, resolves dependencies, handles expected exceptions, validates response models, and creates OpenAPI documentation. Uvicorn is the ASGI server. It runs the FastAPI application, accepts network connections, and manages concurrency and workers. The business logic is our own application code. It decides what the endpoint actually does, such as calling services, performing database operations, or applying authentication and authorization rules shown in the diagram. The request reaches the FastAPI application through the server. FastAPI prepares the validated inputs and calls the endpoint. The business logic performs the work and returns data. FastAPI then validates and serializes that result before the client receives JSON. The benefit is clear ownership. The downside is that developers must understand several layers, but each layer has a focused job.

115. What is middleware in a Python web API?API DesignEasy

Question Details

Define middleware as code that wraps request handling so it can inspect or change an incoming request, call the next handler, and inspect or change the response. Explain ordering, short-circuiting, exception boundaries, and common uses such as request IDs, logging, timing, CORS, authentication, and compression. Relate the concept to ASGI or FastAPI without making it framework-specific.

Short Interview Answer (30-60 seconds)

At a high level, middleware is code that wraps my API request handling. The client request passes through middleware before reaching the route handler. Each layer can inspect or change the request, call the next handler, or stop early. After the route creates a response, the response returns through the middleware in reverse order. Common uses include request IDs, logging, timing, authentication, CORS, compression, and rate limiting. The benefit is reusable shared behavior. The trade-off is that ordering and error handling become more important.

Detailed Explanation

Middleware is helper code around normal API request handling. It lets us put common work in one place instead of repeating it inside every route. In this diagram, the client request moves through several middleware layers before reaching the route handler. A layer can inspect the request, change it, continue processing, or stop early. After the route produces a response, that response travels back through the middleware in reverse order. The main challenge is keeping this order clear while handling shared work and errors safely.

Useful Questions to Ask the Interviewer
  • Should I explain middleware mainly as a general web API concept?
  • Should I also relate the idea to ASGI and FastAPI?
  • Should I cover ordering, early responses, and exception handling?
What is middleware in a Python web API? diagram
How to Explain It in an Interview
1. Start with the basic idea

I would start by saying that middleware wraps request handling. The client sends a request toward the API. Before the Route Handler runs, the request passes through the middleware stack.

Each middleware can inspect or change the incoming request. It can also perform shared work that many routes need. This keeps that work outside the endpoint function or view.

2. Follow the request through the stack

In this diagram, the request moves from Middleware 1 to Middleware 2 and then Middleware N. The exact stack order depends on how an application builds its middleware chain, but this diagram clearly shows that request order.

Middleware 1 represents Request ID and Logging work. A request ID helps track one request across processing. Logging records useful request or response information.

Middleware 2 represents an Authentication Check. Authentication means checking who the caller is. If processing should continue, this layer calls the next handler.

Middleware N represents another shared concern, such as Compression or CORS. CORS means rules that control browser requests coming from another web origin.

3. Explain short-circuiting

A middleware does not always have to call the next handler. It may return a response early and stop the chain. This behavior is called short-circuiting.

For example, an authentication layer may stop a request that should not continue. In that case, later middleware and the Route Handler do not process that request. This avoids unnecessary work and keeps shared checks outside route code.

4. Explain the route and response path

If every middleware continues, the request reaches the Route Handler. The handler runs the endpoint function or view and produces the API response.

The response then travels back through the middleware in reverse order. Middleware N sees the returning response before Middleware 2, and Middleware 1 is one of the outer layers before the response reaches the Client.

This reverse path lets middleware perform work after downstream processing. Timing middleware can measure elapsed time. Logging can record completion information. Compression middleware can compress the response body before it is sent.

5. Explain exception boundaries

Middleware can also create an exception boundary around later processing. An exception is an unexpected error while handling the request.

The diagram's ASGI-style example calls the next application inside a try block. If downstream processing raises an exception, that middleware can catch it and send an error response. This keeps the error-handling decision around the wrapped application instead of repeating it in every route.

6. Relate the idea to ASGI and FastAPI

ASGI is a standard interface used by Python web applications and servers. An ASGI middleware can itself behave like an ASGI application while wrapping another ASGI application.

The diagram's SimpleMiddleware stores the next application in self.app. Its __call__ method receives scope, receive, and send, performs work before the next application, and then calls await self.app(scope, receive, send).

For real response inspection or modification at the ASGI message level, middleware commonly wraps the send callable so it can observe outgoing response messages. FastAPI is built on ASGI, so this same middleware concept applies there without being specific to FastAPI.

7. Finish with common uses and the trade-off

I would finish with the common uses shown in the diagram: Request IDs, Logging, Timing or Metrics, Authentication, CORS, Compression, and Rate Limiting.

The benefit is reuse and cleaner route handlers. The downside is added request processing and less obvious control flow. Ordering matters because an outer middleware may need to observe work performed by inner layers. Keeping each middleware focused on one clear job makes the design easier to understand.

Practical Complexity & Trade-offs

The main design choice is deciding what belongs in middleware and where each layer sits. The benefit is reuse. Request IDs, logging, timing, authentication, CORS, compression, and rate limiting can work across many routes without repeated code. The downside is extra processing for every request that passes through those layers. Ordering also matters. The request moves through middleware toward the Route Handler, while the response returns through those layers in reverse order. Short-circuiting can stop unwanted work early. Exception boundaries can handle errors from later processing. This is useful, but too many middleware layers can make debugging harder. We accept that trade-off because shared behavior stays separate from normal route code.

Why Interviewers Ask This

Interviewers ask this question to test whether you understand the complete API request and response path. They want more than a definition. They look for correct reasoning about middleware ordering, calling the next handler, short-circuiting, reverse response flow, and exception handling. They also want to see whether you can choose good middleware responsibilities, such as logging, authentication, timing, CORS, compression, and request IDs, while keeping route handlers focused on their main work.

Interviewer may ask next
What happens if authentication middleware rejects a request before it reaches the route handler?

The authentication middleware can stop the request and return a response without calling the next handler. This is short-circuiting. In the diagram, the request reaches Middleware 2 after passing through Middleware 1. Middleware 2 performs the Authentication Check. If that layer decides processing should stop, Middleware N and the Route Handler do not receive the request.

The response then travels back through the middleware layers that already wrapped the call. This means an outer layer, such as Request ID or Logging middleware, can still observe that the request finished early if its implementation is designed to do so.

The benefit is that rejected requests do not waste work in later layers or route code. It also keeps a shared authentication check outside individual routes. The downside is that middleware ordering becomes important. If logging must record rejected requests, the logging layer must wrap the authentication layer. The rest of the request and response design remains unchanged.

How do timing and compression middleware work when the response comes back?

Timing and compression both wrap downstream processing, but they do different jobs. Timing middleware can record a start time before it calls the next handler. After downstream processing finishes, it calculates the elapsed time and records that measurement. This is why timing naturally works around the request and response flow.

Compression middleware mainly affects the outgoing response. The request passes through it toward the Route Handler. When response data comes back, the middleware can compress the response body before the Client receives it. In a raw ASGI implementation, response-aware middleware commonly wraps the send callable so it can observe or change outgoing response messages.

Correctness depends on preserving the middleware order shown by the stack. Requests move inward toward the Route Handler, and response handling moves outward in reverse order. The benefit is reusable response processing outside route functions. The downside is extra CPU work and more middleware behavior to understand when debugging.

116. How would you design a FastAPI endpoint to handle timeouts and partial failures?API DesignHard

Question Details

Design a FastAPI endpoint that calls several downstream services. Explain connect and read timeouts, cancellation, retries, circuit breakers, partial responses, error mapping, request tracing, idempotency, and how to avoid leaving inconsistent state.

Short Interview Answer (30-60 seconds)

At a high level, I would design the FastAPI endpoint as a resilient aggregator. The client calls POST /aggregate with an Idempotency-Key. The service validates the request, checks idempotency, then fans out calls to downstream services using connect and read timeouts. The orchestrator uses retries with backoff, cancellation, and caller side circuit breakers. It maps each success or failure into a partial response. The trade-off is that users may receive incomplete data, but the endpoint stays fast and predictable.

Detailed Explanation

The goal is to build a FastAPI endpoint that calls several downstream services without letting one slow service break the whole request. The main challenge is to handle timeouts, retries, and partial failures while keeping state consistent. The diagram solves this with an aggregator, idempotency, caller side resilience, tracing, and a partial response builder.

Useful Questions to Ask the Interviewer
  1. Which clients and core use cases must the API support?
  2. What authentication, authorization, and data-validation rules should I assume?
  3. What scale, error handling, idempotency, and versioning requirements matter?
How would you design a FastAPI endpoint to handle timeouts and partial failures? diagram
How to Explain It in an Interview
1. Start with the API boundary

I would start by saying this endpoint is an aggregation API. The client sends an HTTPS request to POST /aggregate. It also sends an Idempotency-Key, which lets retries reuse the same logical request.

The request enters the FastAPI Service and goes through Request Validation. Validation checks that the request shape is acceptable before any downstream call is made. This avoids wasting work on invalid input.

2. Check idempotency before doing work

The Idempotency Check talks to the Idempotency Store. The store checks whether the same key already has a completed result or a processing lock. This protects the endpoint when a client retries after a timeout.

If a completed result already exists, the service can return it safely. If the request is new, the service continues into the Orchestrator / Aggregator. After the final response is built, the completed response can be stored for safe retry.

3. Call downstream services with clear limits

The Orchestrator / Aggregator makes fan-out parallel calls. Fan-out means calling several services during one user request. In the diagram, it calls User Service, Profile Service, and Recommendation Service over HTTPS plus mTLS.

mTLS means both sides prove their identity during the encrypted connection. Each call has a connect timeout and a read timeout. The connect timeout protects connection setup. The read timeout protects the wait for the response body.

4. Handle failures inside the orchestrator

The orchestrator owns retries with backoff. Backoff means waiting a little longer between retry attempts. This helps with short failures without flooding a bad dependency.

It also owns cancellation. If the client disconnects or the overall request deadline is reached, in-flight downstream calls should be cancelled. The caller side circuit breaker is also inside the orchestrator. It fails fast when a downstream service is unhealthy.

5. Build the partial response

Downstream services return success, error, or timeout. The orchestrator sends collected successes and failures to the Error Mapping & Partial Response Builder. That builder maps exceptions to HTTP friendly error details.

The response can contain available data and per service status. For example, user data may succeed while profile times out. The client still receives an HTTPS partial response instead of waiting forever.

6. Keep state consistent and observable

The Primary Database should store only confirmed successful state. Side effects should be published to the Message Broker only after a successful commit. This avoids recording failed partial data as success.

Tracing / Observability receives trace spans, logs, and metrics. This gives end to end visibility through request_id and spans. Configuration Service provides timeouts, retries, and circuit breaker policy. Secret Manager provides mTLS certificates, tokens, and API keys.

The main trade-off is user experience versus completeness. Partial responses keep the endpoint responsive, but the client must understand which services failed.

Practical Complexity & Trade-offs

The benefit is that one slow downstream service does not block the whole endpoint. Connect and read timeouts protect latency. Retries with backoff handle short failures, but too many retries can increase load. Caller side circuit breakers reduce cascading failures, but they may return degraded data while a service recovers. Idempotency makes client retries safer, but it needs a store and careful response handling. Persisting only confirmed successful state avoids inconsistent data. Publishing side effects after commit is safer, but it adds operational complexity.

Why Interviewers Ask This

Interviewers ask this to test API reliability judgment. They want to know if the candidate can separate request validation, downstream calls, response mapping, and side effects. They also check whether the candidate understands timeouts, retries, cancellation, circuit breakers, tracing, idempotency, and consistent state. A strong answer explains trade-offs instead of only naming tools.

Interviewer may ask next
What changes if the Profile Service is slow for several minutes?

I would keep the same endpoint and make the caller side circuit breaker protect the Profile Service call. The affected flow is Orchestrator / Aggregator to Profile Service. After enough failures or timeouts, the circuit breaker opens and the orchestrator stops calling that service for a short period.

During that time, the Error Mapping & Partial Response Builder returns a partial response. It can include user data and recommendations if they succeed, while marking profile as unavailable or timed out. Tracing / Observability records the failures so the team can investigate.

Correctness is maintained because failed profile data is not stored as successful state. The downside is that users may see missing profile data until the breaker half opens and the service recovers.

How would you avoid duplicate side effects when the client retries the request?

I would use the Idempotency-Key as the stable identity for the client operation. The affected flow is Client / Web App to Idempotency Check and Idempotency Store. Before doing downstream work, the service checks whether the key already has a result or a processing lock.

If the same request is already complete, the stored response is returned. If it is still running, the API can return the current processing state or prevent duplicate execution. After building the response, the completed result is stored for safe retry.

For state changes, the service persists only confirmed successful state. It publishes side effects after successful commit. The downside is that the idempotency store must be reliable and must expire old keys carefully.

117. How would you secure communication between microservices?API DesignHard

Question Details

Design secure service-to-service communication for Python APIs. Explain workload identity, mutual TLS, token validation, authorization, certificate rotation, secret management, replay protection, network policies, audit logging, and how trust is maintained across environments.

Short Interview Answer (30-60 seconds)

At a high level, my goal is to allow only trusted services to communicate. I would separate the design into workload identity, short-lived credentials, encrypted transport, request authorization, and audit logging. Service A gets a trusted identity and a short-lived JWT. It then calls Service B through the two service-mesh proxies using HTTPS, mTLS, and the bearer token. Service B validates the token and permissions before processing the request. The response returns from Service B through the B-side proxy and A-side proxy to Service A. The trade-off is stronger security with more operational complexity.

Detailed Explanation

The goal is to secure every call between Service A and Service B. The main challenge is proving both service identities while also checking whether the caller may perform the requested action. The diagram solves this with workload identity, short-lived certificates, OAuth tokens, mTLS, authorization rules, network controls, and audit logging.

Useful Questions to Ask the Interviewer
  1. Which clients and core use cases must the API support?
  2. What authentication, authorization, and data-validation rules should I assume?
  3. What scale, error handling, idempotency, and versioning requirements matter?
How would you secure communication between microservices? diagram
How to Explain It in an Interview
1. Start with the security goal

I would begin by saying that network location is not enough to trust a service. Every workload must prove its identity before making or accepting a call. The design therefore uses two checks. Mutual TLS authenticates the communicating workloads and encrypts the connection. The JWT carries the caller's application identity, audience, scopes, and other claims used by Service B.

Both checks must succeed. mTLS does not decide whether Service A may perform a business action. The token alone also does not protect traffic while it moves across the network.

2. Establish workload identity

The Workload Identity system checks Service A and Service B. It then gives each workload a trusted SPIFFE identity or OIDC subject. This identity represents the running service instead of a person or shared password.

The Certificate Authority uses this trusted identity to issue short-lived X.509 or SPIFFE certificates. It provides one certificate to the A-side proxy and another to the B-side proxy. These certificates rotate automatically. The proxies reload them without requiring manual key changes.

Separate environments use different trust domains, certificate authorities, issuers, policies, and keys. Development workloads therefore cannot automatically become trusted production workloads.

3. Obtain the access token

Before calling Service B, Service A sends a token request to the Authorization Server. The request uses Service A's workload identity through client credentials or token exchange. The Authorization Server verifies that identity before issuing a short-lived JWT.

The token is restricted to Service B through its audience claim. It also contains the scopes or claims needed for authorization. The Authorization Server publishes JWKS, which is a set of public keys used to verify the token signature. Service A attaches the JWT as a bearer token when making the service call.

4. Send the request through mutual TLS

Service A uses its Python client, such as requests or httpx, to call Service B. The request first reaches the A-side proxy. It then travels to the B-side proxy over HTTPS with mTLS and the bearer JWT.

The A-side proxy verifies Service B's certificate. The B-side proxy verifies Service A's certificate. This mutual check proves both workload identities and encrypts the traffic. Network Policies also allow only the required Service A to Service B path. All other traffic is denied by default.

If mTLS verification fails, the connection is rejected before the business request reaches Service B.

5. Validate and authorize the request

After the B-side proxy accepts the secure connection, Service B validates the JWT. It checks the signature using JWKS. It also checks the issuer, audience, expiration time, not-before time, token identifier, and required scopes.

An invalid or expired token returns 401. A valid token proves the caller's identity, but Service B must still authorize the request. It uses RBAC or ABAC rules to decide whether the caller may perform the action. A denied authorization decision returns 403.

For sensitive one-time operations, replay protection may check a nonce or token identifier against a replay cache. Write operations may also use idempotency keys so the same retried request does not create duplicate work.

6. Process the request and return the response

After authentication and authorization succeed, Service B processes the request. Its FastAPI application returns the shown success or error response. The response may use JSON or Protobuf.

The response direction is the reverse of the request. It moves from Service B to the B-side proxy, then to the A-side proxy, and finally back to Service A. The response remains protected by the established mTLS connection.

Service A handles the returned result or error. It does not treat audit logging as part of the business response path.

7. Explain secrets, logging, failures, and trade-offs

The Secret Manager stores application secrets such as database credentials, API keys, and encryption keys. It is not the normal source of the short-lived workload certificates used by the proxies.

Service A, Service B, and the proxies send audit events, traces, and metrics to the Observability and Audit system. This creates a record of service calls, identity checks, authorization decisions, and failures.

If the Authorization Server is unavailable, it does not issue new tokens. Existing valid tokens may continue until they expire. Service B may use previously validated JWKS only within the configured cache lifetime. If no valid signing key is available, validation fails closed. The main trade-off is that this design greatly reduces trust risk, but it requires certificate rotation, token management, policy management, monitoring, and reliable identity infrastructure.

Practical Complexity & Trade-offs

The benefit is that one stolen password is not enough to enter the system. Each service gets its own short-lived identity, certificate, and access token. mTLS protects the connection, while the JWT and authorization rules control the requested action. This is safer, but it adds more systems to operate. The team must maintain the identity provider, Authorization Server, Certificate Authority, proxies, policies, and audit tools. Short-lived credentials reduce the damage from a stolen key, but they must rotate reliably. Default-deny network rules reduce unwanted access, but incorrect rules can block valid traffic. We accept this complexity because service identity, encryption, least privilege, and audit records are important security controls.

Why Interviewers Ask This

The interviewer wants to see whether the candidate understands that secure communication needs several separate controls. A strong answer distinguishes workload identity, transport encryption, token validation, and business authorization. It also shows correct ownership between services, proxies, identity systems, certificate authorities, and audit tools. The interviewer is also checking failure handling, short-lived credential rotation, replay protection, network restrictions, and whether the candidate can explain security trade-offs without claiming perfect protection.

Interviewer may ask next
What happens if the Authorization Server becomes unavailable while services are still running?

I would fail closed for new token issuance, but I would not immediately stop every existing call. The Authorization Server would stop issuing new JWTs because it cannot safely verify and sign new requests. Service A could continue using an already-issued token only until that token expires.

Service B would still validate every token. It would check the signature, issuer, audience, time claims, and scopes. It could use previously validated JWKS from its cache, but only within the configured cache lifetime. If the required public signing key is missing or no longer valid, Service B must reject the request instead of bypassing validation.

The mTLS connection can still prove the workload identities, but mTLS does not replace the missing business authorization token. The team should monitor token issuance failures and restore the Authorization Server quickly. The main downside is that short token lifetimes improve security but give the team less time to recover before valid service calls begin failing.

How would you prevent a captured service request from being replayed?

I would keep the same mTLS and JWT design, but add stronger protection for sensitive operations. TLS protects the request while it travels across the network. The JWT should also have a short lifetime and a narrow audience so it cannot be reused broadly.

For a sensitive one-time action, Service A can include a nonce or unique request identifier. Service B checks that value against a replay cache. If the same identifier appears again during the allowed time window, Service B rejects the duplicate request. Merely checking that the JWT contains a jti claim is not enough. The server must remember which values were already used.

For write operations that may be retried normally, I would use an idempotency key. Service B stores the previous result for that key and returns the same result instead of repeating the write. The downside is extra storage and cleanup work for replay records and idempotency keys.

118. How would you design a fault-tolerant API?API DesignHard

Question Details

Design an API that remains useful when dependencies are slow or unavailable. Explain timeouts, bounded retries, exponential backoff, circuit breakers, bulkheads, graceful degradation, idempotency, caching, health checks, load shedding, and observability.

Short Interview Answer (30-60 seconds)

At a high level, I would design the API so one slow dependency cannot bring down the whole request path. Requests move through edge protection, a load balancer, the API gateway, and then the Business Service. Every outbound call uses deadlines, bulkheads, a local circuit breaker, bounded retries, per-attempt timeouts, and exponential backoff with jitter. The service can use cached data, partial responses, or asynchronous processing when safe. The trade-off is more operational complexity and sometimes slightly higher latency.

Detailed Explanation

The goal is to keep the API useful when a dependency becomes slow or unavailable. The main challenge is preventing one failure from spreading across the whole system. I would explain the design by following the request path shown in the diagram.

Useful Questions to Ask the Interviewer
  1. Which clients and core use cases must the API support?
  2. What authentication, authorization, and data-validation rules should I assume?
  3. What scale, error handling, idempotency, and versioning requirements matter?
How would you design a fault-tolerant API? diagram
How to Explain It in an Interview
1. Start with the main request path

The request begins from a mobile app, web app, or third-party client. It first passes through DNS, the CDN, the WAF, and DDoS protection. These edge systems block harmful traffic and reduce unnecessary load.

The request then reaches the global or regional load balancer. The load balancer sends it to a healthy API Gateway instance. The gateway handles authentication, authorization, request validation, rate limiting, load shedding, idempotency keys, and correlation IDs.

Load shedding means rejecting extra work before the system becomes unstable. The gateway may return 429 when a client exceeds a limit. It may return 503 when the system cannot safely accept more work.

2. Process the request in the Business Service

The API Gateway forwards valid requests to the Business Service. This service owns the business logic. It also decides whether the request needs cached data, another internal service, an external provider, or asynchronous processing.

The Business Service checks the distributed cache when cached data is useful. A valid cache hit can avoid a slower dependency call. On a cache miss or expired value, the service calls the required dependency.

The service may call User Service, Payment Service, Inventory Service, or an External API. User Service owns User DB. Payment Service owns Payment DB. Inventory Service owns Inventory DB. The third-party provider is accessed only through its external API.

3. Protect every outbound dependency call

Every outbound call from the Business Service uses a resilience policy. The overall request deadline limits the total request time. A per-attempt timeout limits each individual dependency call.

A bulkhead limits concurrent calls to one dependency. This prevents a failing service from consuming every connection or worker. A local circuit breaker watches recent failures. It fails fast when the dependency appears unhealthy.

Retries are bounded. The service retries only safe or idempotent operations. An idempotent operation can be repeated without causing the action twice. Write requests can use an idempotency key to prevent duplicate processing.

Retries are used only for transient failures. The service waits using exponential backoff with jitter. The delay grows after each failure, while jitter adds randomness. This reduces retry storms. All retries must remain inside the overall deadline.

4. Degrade gracefully when possible

The Business Service should not fail the complete request when optional data is unavailable. It may return stale cached data when that data is still safe. It may also return static fallback data.

For optional fields, the service can return a partial response with warnings. A useful degraded response can still return 200. The response should clearly identify unavailable fields so the client does not treat missing data as complete data.

Some write operations can be queued for later. The service publishes an event to the message broker and returns 202 Accepted. The response should include an operation identifier or status URL when supported.

The broker uses at-least-once delivery. Therefore, consumers such as email workers and analytics workers must be idempotent. When an essential dependency is unavailable and no safe fallback exists, the API fails fast with 503.

5. Monitor health and failures

Every component sends metrics, logs, and traces to the observability systems. The correlation ID connects events from the gateway, Business Service, and dependencies.

Important metrics include latency, traffic, errors, saturation, retries, timeouts, circuit-breaker state, and cache hit ratio. Dashboards show system behavior. Alerts notify the team when thresholds are crossed.

Liveness checks show whether a process is running. Readiness checks show whether an instance should receive traffic. Configuration, secrets, feature flags, and service routing belong to the health and control plane.

6. Explain the trade-off

The benefit is better availability and controlled failure. A slow dependency is less likely to cause a complete outage. The downside is more configuration, monitoring, testing, and operational work. Retries may also increase dependency load when configured badly. I would accept this complexity because predictable degradation is safer than uncontrolled cascading failure.

Practical Complexity & Trade-offs

The benefit of this design is that one failing dependency does not automatically break the whole API. Timeouts stop calls from waiting forever. Bulkheads protect connections and workers. Circuit breakers stop repeated calls to an unhealthy service. Bounded retries can recover from short network problems. Backoff and jitter reduce retry storms. Caching improves speed and can provide safe fallback data. Load shedding protects the system during heavy traffic. The downside is additional code, configuration, and monitoring. Retry rules must be tested carefully because retries can increase load. Partial responses also make the client contract more complex. We accept these costs because the API remains predictable during failures.

Why Interviewers Ask This

Interviewers ask this question to test engineering judgment rather than memorized definitions. They want to see whether the candidate can control cascading failures, define clear service boundaries, and choose safe fallback behavior. They also evaluate correct use of timeouts, retries, idempotency, caching, load shedding, status codes, health checks, and observability. A strong answer explains both availability benefits and operational trade-offs.

Interviewer may ask next
What would you change if the Payment Service became slow during peak traffic?

I would protect the Payment Service without changing the rest of the architecture. The Business Service would keep a strict overall request deadline and a shorter per-attempt timeout for payment calls. A dedicated bulkhead would limit payment concurrency, so slow payment requests could not consume every worker or connection. The local circuit breaker would open when payment failures or timeouts cross the configured threshold.

I would avoid broad retries because payment operations can create duplicate charges. A retry would happen only when the operation uses an idempotency key and the failure is classified as transient. The retry must remain inside the request deadline.

If immediate payment confirmation is essential, the API should fail fast with 503 when no safe result is available. It should not pretend that payment succeeded. If the business contract supports delayed processing, the API can return 202 and queue the operation with an operation ID. The downside is that strict limits may reject some requests during a temporary slowdown, but this protects the wider system from cascading failure.

How would you keep asynchronous consumers correct when the message broker delivers the same event more than once?

I would make every consumer idempotent because the broker provides at-least-once delivery. Each event should include a unique eventId, event type, version, occurrence time, correlation ID, and payload. Before applying a side effect, the consumer checks whether that eventId was already processed.

For example, the Email Worker should not send the same message twice. It can store the processed eventId with the result of the first operation. When the same event arrives again, the worker acknowledges it without repeating the email.

The consumer should acknowledge the broker message only after successful processing. Temporary failures can use bounded retries. Repeated failures can move the event to a dead-letter path for investigation. Logs and traces should include the eventId and correlation ID.

The main downside is extra storage and cleanup for processed event records. There is also more consumer logic. However, this is required because the design does not claim exactly-once delivery.

119. What is profiling in Python?PerformanceEasy

Question Details

Define profiling as measuring where a running Python program spends time and resources. Explain deterministic profiling with cProfile, sampling profilers, wall time versus CPU time, function call counts, memory and allocation measurement, I/O waits, representative inputs, baselines, and why optimization should target a measured bottleneck and be verified with the same workload.

Short Interview Answer (30-60 seconds)

I would first measure the program with a representative workload and save a baseline. Profiling means finding where a running Python program spends time and resources. I can use cProfile for deterministic function call timing in a controlled run, or a sampling profiler when I need lower overhead on a running process. I would compare wall time, CPU time, call counts, memory and allocation data, and input and output waits, then optimize only the measured bottleneck and profile again with the same workload. The tradeoff is that detailed profiling can add overhead, while sampling gives an estimate.

Detailed Explanation

I start by measuring the same real work that users or jobs normally do. I record how long it takes and how much computer power and memory it uses. Then I look for the part that takes the most time or uses the most resources. I do not guess. I change only the measured slow part, run the same work again, and compare the result with the first measurement. This keeps the test fair and helps me see whether the change really helped without moving the problem somewhere else.

Useful Questions to Ask the Interviewer
  1. Are we profiling a local program, a service request, or a background job?
  2. Is the main problem slow response time, high CPU use, high memory use, or waiting for input and output?
  3. Can we reproduce the issue with representative inputs and the same environment?
  4. Do we need low overhead observation of a running process, or can we use a controlled profiling run?
What is profiling in Python? diagram
How to Explain It in an Interview

Profiling is the process of measuring where a running Python program spends time and resources. I first define the symptom and measurement boundary. For example, I may measure one representative program run from start to finish, including its normal input and output waits. I save a baseline before changing code.

Wall time is the real elapsed time from start to finish, so it includes waiting. CPU time is the time the CPU spends executing code, so waiting time is excluded. Function call counts show how often functions run. Memory and allocation profiling shows where Python memory is used and where objects are created. Input and output wait time shows time spent waiting for files, network calls, databases, or other external work.

For deterministic profiling in a controlled run, I can use cProfile. It records Python function calls and timing, which is useful for finding functions with high total time or cumulative time. Its main limitation is overhead because it observes function calls directly. For lower overhead observation of a running Python process, I can use a sampling profiler such as py spy. It takes periodic snapshots, so it estimates where time is spent and can miss very short events.

No single profiler measures everything. cProfile is not a memory allocation profiler. For Python allocation tracing, tracemalloc can compare snapshots and show where Python allocations come from, but it does not track every native allocation. Application metrics and traces can also help separate Python execution from database, network, queue, lock, or event loop waiting when those parts are inside the measurement boundary.

I use representative requests, jobs, data sizes, and dependency behavior. I do not treat one profiler run or one small timing test as final proof. After the evidence identifies the real bottleneck, I make one focused change. Then I rerun the same representative workload and compare with the baseline. I check the same timing and resource measures, verify that the program output is still correct, and confirm that the bottleneck was reduced instead of moved to another part of the system. After deployment, I continue watching the same production metrics when production monitoring is available.

Technical Approach
  1. Define the symptom and measurement boundary, such as total elapsed time, CPU use, memory use, or input and output waiting for one representative run.
  2. Capture a baseline before changing code.
  3. Reproduce the same workload with representative inputs and dependency behavior.
  4. Use the right evidence source. Use cProfile for deterministic function call timing in a controlled run, a sampling profiler for lower overhead observation, and a memory allocation profiler when memory is the problem.
  5. Compare wall time, CPU time, function call counts, memory and allocation data, and input and output waits to classify the bottleneck from evidence.
  6. Change only the measured bottleneck.
  7. Run the same workload again and compare with the baseline.
  8. Verify correct output and check that the bottleneck did not move elsewhere.
  9. Monitor the same important metrics after deployment when production monitoring is available.
Practical Insights

Profiling has a cost. cProfile adds extra work because it records function calls, so the measured run can be slower than normal. A sampling profiler usually has lower overhead, but it gives an estimate and may miss very short work. Memory tracing also uses extra memory and CPU. The test itself takes time because the before and after workloads should be the same and representative. The optimization may also trade one resource for another, such as using more memory to reduce CPU work, so I compare the full set of relevant measurements after the change.

Why Interviewers Ask This

Interviewers ask this to see whether I measure before I optimize. They want to know if I can separate real elapsed time, CPU work, function calls, memory use, allocations, and waiting for input and output. They also want to see whether I choose a suitable profiler, understand its limits, use representative inputs, create a baseline, and verify a change with the same workload.

Common interview mistakes

Common mistakes are optimizing before measuring, profiling unrealistic input, comparing different workloads before and after a change, confusing wall time with CPU time, treating cProfile as a memory profiler, assuming one profiler explains every delay, trusting one sample as final proof, using a small timing experiment as proof of whole service performance, ignoring database or network waiting, and forgetting to verify correct output after optimization. Another mistake is improving one function while moving the bottleneck to memory, input and output, or another dependency.

Interview tip

Explain profiling as a measurement loop. Start with the symptom, save a baseline, use representative input, choose the profiler that answers the right question, find the measured bottleneck, change that part, and verify with the same workload. Mention that cProfile gives detailed function timing with overhead, while sampling gives a lower overhead estimate.

Interviewer may ask next
What if wall time is high but cProfile shows little CPU time?

That usually means the measured workload may be spending much of its time waiting rather than executing Python code. For the same representative program run, I would keep the start to finish wall time boundary and inspect input and output waits such as files, network calls, database work, queues, or other blocking operations. cProfile function timing alone may not explain an external wait, so I would combine it with application timing, tracing, or dependency metrics. The key tradeoff is adding enough instrumentation to locate the wait without adding so much overhead that it changes the workload.

When would you choose a sampling profiler instead of cProfile?

I would choose a sampling profiler when I need lower overhead observation of the same running Python workload, especially when stopping the process for a controlled cProfile run is not practical. The measurement boundary stays the same representative process and workload, but the sampling profiler takes periodic snapshots instead of recording every Python function call. This matters because it usually disturbs the process less. The tradeoff is that the result is an estimate and very short functions may be missed, so I still verify any optimization with the same workload and baseline.

120. Which Python profiling tools would you use to investigate CPU, memory, and latency problems?PerformanceHard

Question Details

Explain when to use cProfile, pstats, timeit, py-spy, tracemalloc, memory profilers, application metrics, and distributed tracing, and how you would avoid drawing conclusions from unrealistic microbenchmarks.

Short Interview Answer (30-60 seconds)

I would start with production symptoms and metrics, not a profiler. First I would check p95 latency, error rate, CPU use, memory growth, queue delay, and dependency timing. Then I would choose the tool based on the suspected bottleneck. I would use cProfile and pstats for function level CPU work, py spy for low overhead sampling, tracemalloc and memory profilers for allocations, and tracing for service latency. I would use timeit only for small isolated checks, not as proof of production speed.

Detailed Explanation

I would treat profiling as an evidence gathering process. The first step is to define the visible problem. For example, the problem may be high p95 latency, growing memory, high CPU use, or slow background jobs. Then I would set the measurement boundary. That means deciding whether I am measuring one function, one request, one worker job, or the whole service.

Useful Questions to Ask the Interviewer
  1. What user-visible symptom and measurable performance target define success?
  2. What workload, environment, data size, and concurrency level should I assume?
  3. What profiling evidence is available, and which tradeoffs or system changes are allowed?

For latency, I would start with application metrics and distributed tracing. Application metrics show trends such as request rate, p95 latency, p99 latency, error rate, CPU use, and memory use. Distributed tracing follows one request across services, databases, queues, and network calls. This helps separate time spent doing Python work from time spent waiting.

For CPU problems, I would use cProfile in a controlled run. It is deterministic, which means it records function calls and time during the run. Then I would use pstats to sort the output by total time and cumulative time. Total time shows time inside one function. Cumulative time includes time spent in functions it calls. If I need to observe a running process with less overhead, I would use py spy. It is a sampling profiler, which means it checks stack frames at intervals instead of recording every call.

For memory problems, I would use tracemalloc first when Python allocations are suspected. It can compare snapshots and show where memory was allocated. If memory grows line by line in a script or worker, I would use a memory profiler. I would remember that tracemalloc does not see every native allocation from C extensions.

For tiny code experiments, I would use timeit. It is useful for checking a small function in isolation. I would not use it to prove that a web endpoint is faster. A microbenchmark may ignore database time, network delay, serialization, cache behavior, and concurrency.

After finding evidence, I would make one targeted change. Then I would retest with the same representative workload. I would compare before and after latency, CPU, memory, errors, and dependency time. I would also verify that results stay correct. The goal is to reduce the measured bottleneck without moving the problem somewhere else.

Which Python profiling tools would you use to investigate CPU, memory, and latency problems? diagram
Technical Approach

First, define the symptom. Use a concrete metric such as p95 latency, CPU use, memory growth, or queue delay. Second, capture a baseline from metrics, logs, and traces. Third, reproduce the problem with representative requests, data sizes, and dependency behavior. Fourth, classify the bottleneck using evidence. It may be CPU work, memory allocation, database time, network wait, queue delay, locks, or event loop blocking. Fifth, choose the tool that matches the suspected problem. Use cProfile and pstats for controlled CPU runs. Use py spy for sampling a running process. Use tracemalloc and memory profilers for memory growth. Use distributed tracing for end to end latency. Sixth, make one targeted change. Seventh, retest with the same workload and verify correctness.

Practical Insights

The cost depends on the tool. Application metrics and traces are useful in production, but they can add small overhead and sampling bias. cProfile gives detailed CPU data, but it can slow the program during a controlled run. py spy has lower overhead, but it may miss very short events. tracemalloc helps with Python allocations, but it may miss memory held by native libraries. timeit is cheap for small code, but it does not include real service behavior. The main cost is running realistic tests and comparing results carefully.

Why Interviewers Ask This

Interviewers ask this to check whether you measure before optimizing. They want to see if you can pick the right tool for CPU, memory, and latency symptoms. They also want to know if you understand tool limits. A strong candidate does not trust one small benchmark. They compare real metrics, traces, and profiler evidence.

Common interview mistakes

A common mistake is optimizing code before measuring the real symptom. Another mistake is using average latency and ignoring p95 or p99 latency. Some candidates use timeit on a tiny function and treat it as proof that the full service is faster. That is risky because production includes databases, network calls, queues, payload sizes, and concurrency. Another mistake is using cProfile once and assuming it explains memory or network delay. Some people confuse CPU time with waiting time. Others ignore memory growth, allocation churn, connection pool waits, lock contention, or event loop blocking. A final mistake is changing the workload between before and after tests.

Interview tip

Start with the symptom and metric. Then name the tool that answers that exact question. Explain one limitation for each tool. Say that timeit is useful for small isolated checks, but real performance needs representative load, metrics, traces, and before and after comparison.

Interviewer may ask next
What if timeit shows that a function is faster, but the endpoint is still slow in production?

I would not treat the timeit result as the final answer. timeit only measures a small isolated piece of code. The endpoint may still be slow because it waits on the database, network calls, serialization, middleware, or a queue. I would go back to production metrics and distributed tracing. The measurement boundary should be the full request, not just the small function. If traces show that most time is spent in a database call, optimizing Python loop speed will not help much. The tradeoff is that full service measurement takes more setup than a microbenchmark. But it gives evidence that matches real user latency.

What if memory keeps growing but cProfile does not show the cause?

I would switch tools because cProfile is mainly for CPU profiling. It tells me where function time is spent, not where memory is being retained. For Python allocation growth, I would use tracemalloc snapshots. I would compare snapshots before and after the workload to find allocation sources. If I need line level growth, I would use a memory profiler. I would also check whether native libraries hold memory, because tracemalloc may not see every native allocation. The tradeoff is that memory tools add overhead and need representative input sizes. The result is still stronger than guessing from CPU data.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Content Accuracy and Verification: To the fullest extent permitted by applicable law, we do not represent or warrant that interview guides, questions, answers, examples, or diagrams are accurate, complete, current, error-free, or suitable for any particular purpose. You are responsible for independently reviewing and verifying the information before relying on it.