šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

The Python httpx Library

httpx as the modern successor to requests — an (almost) identical sync API, plus genuine native async support that requests was never designed for.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

What is the key structural difference between httpx.Client and httpx.AsyncClient?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

httpx was built specifically to be requests-compatible where it makes sense, while adding what requests fundamentally can't retrofit: native async support, HTTP/2, and a single, consistent Client class for both sync and async usage. This lesson covers when and why to reach for it.

1Requests-Compatible by Design, With a Deliberate Purpose

httpx's sync API was deliberately designed to closely mirror requests' — httpx.get(url, timeout=5), response.raise_for_status(), response.json() all work identically in both libraries, and migrating existing synchronous code between them is often close to a drop-in replacement, changing only the import statement in many cases. This design choice was deliberate, not incidental: httpx's authors specifically wanted developers already familiar with requests to be able to adopt httpx with minimal relearning, since the sync use case (the majority of existing HTTP client code) needed no reinvention.

What httpx adds on top of that familiar foundation is precisely what requests, as an older, more foundational library, cannot retrofit without a breaking rewrite: native async/await support (covered in the next section), built-in HTTP/2 support, and a more modern underlying transport architecture (httpcore) that both libraries' authors have discussed as a natural evolution beyond what requests' original architecture can support.

The practical takeaway for choosing between them on a new project: for a purely synchronous codebase with no foreseeable async needs, requests remains a perfectly reasonable, extremely well-established choice. For any project that either needs async HTTP now, or might plausibly need it later, httpx's identical sync API plus native async support makes it the more forward-compatible choice, avoiding a potential future migration entirely.

āœ•
—
+
import httpx

response = httpx.get("https://api.example.com/data", timeout=5)
response.raise_for_status()
data = response.json()
# Nearly identical to the equivalent requests code -- same method names, same patterns
localhost:3000
Familiar, Compatible API
httpx.get(url, timeout=5).raise_for_status().json()
Nearly identical to requests — deliberately

2One Client Class, Sync or Async, Same Method Names

The structural centerpiece of httpx's design is offering both httpx.Client (synchronous) and httpx.AsyncClient (asynchronous) with the *exact same method names and overall shape* — client.get(), client.post(), the same parameter names for headers, timeouts, and query parameters — differing only in whether those methods are ordinary synchronous calls or coroutines requiring await, tying directly back to the async/await mechanics covered in the Concurrency & Parallelism section.

This parallel design means a developer who already knows httpx's sync Client API has to learn essentially nothing new to use AsyncClient correctly beyond the general async/await mechanics they'd need for any asyncio code — async with httpx.AsyncClient() as client: response = await client.get(url) follows the identical shape as its sync counterpart, just wrapped in the async/await machinery. This is deliberately different from having to learn two genuinely separate libraries (like the earlier common pairing of requests for sync and aiohttp for async) with different APIs, different configuration patterns, and different quirks to remember.

The practical benefit compounds for a codebase that has both sync and async call sites (a CLI tool with some async internals, or a codebase migrating gradually toward async) — using httpx for both means the HTTP-calling code looks and behaves consistently everywhere, rather than requiring developers to context-switch between two entirely different libraries' conventions depending on which part of the codebase they're working in.

āœ•
—
+
import httpx
import asyncio

async def fetch_data():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/data", timeout=5)
        response.raise_for_status()
        return response.json()

asyncio.run(fetch_data())
localhost:3000
Consistent Dual API
Client.get() / AsyncClient.get()
Same shape, same names — sync or async, your choice

3HTTP/2: A Capability requests Structurally Cannot Offer

HTTP/1.1, the protocol version requests (and most of the web historically) has used, has a real limitation for high-concurrency clients: within a single TCP connection, only one request can be in flight at a time (technically, HTTP/1.1 pipelining exists but is poorly supported and rarely used in practice) — making multiple concurrent requests to the same host requires multiple separate connections, each with its own setup overhead.

HTTP/2 introduces true multiplexing at the protocol level: multiple requests and responses can be interleaved over a *single* underlying TCP connection simultaneously, eliminating the need for multiple connections to achieve request concurrency to the same host, and reducing per-request overhead meaningfully for workloads making many concurrent calls to the same API. httpx.Client(http2=True) opts into this behavior when both the client and the target server support it (falling back to HTTP/1.1 transparently otherwise).

This capability exists in httpx specifically because it was built on httpcore, a newer, more capable underlying transport library designed with HTTP/2 support from the start — requests' underlying transport (urllib3, historically) was built around an HTTP/1.1 model, and adding genuine HTTP/2 support would require the kind of fundamental architectural change that motivated building httpx as a new library rather than attempting to retrofit requests itself.

āœ•
—
+
import httpx

client = httpx.Client(http2=True)
response = client.get("https://api.example.com/data")
# HTTP/2, when the server supports it, can multiplex requests
# over a single connection more efficiently than HTTP/1.1
localhost:3000
Protocol-Level Advantage
httpx.Client(http2=True)
True multiplexing over one connection — structurally unavailable in requests

4Step-by-Step Breakdown

requests was never designed for async code — it's a fundamentally synchronous library. httpx offers nearly the same API, plus the async support requests can't provide.

httpx's SYNC API is deliberately almost identical to requests -- migrating existing code is usually a near drop-in replacement.

The SAME httpx.Client class supports async too -- via AsyncClient, using the exact same method names, just awaited.

Checkpoint: What is the key structural difference between httpx.Client and httpx.AsyncClient?

  • →AsyncClient's methods are coroutines that must be awaited, matching Python's async/await syntax; Client's methods run synchronously
  • →They support entirely different, non-overlapping sets of HTTP features

httpx supports HTTP/2 -- a single connection can multiplex multiple concurrent requests, unlike HTTP/1.1's one-request-per-connection-at-a-time model.

Checkpoint: Why is HTTP/2 support (client=httpx.Client(http2=True)) something requests cannot offer?

  • →requests is built on an HTTP/1.1-only underlying transport, with no built-in HTTP/2 support
  • →HTTP/2 is not a real, standardized protocol

httpx's sync usage mirrors requests closely; async patterns deserve their own deeper look, which the Async HTTP Clients lesson provides.

Match Real Sync and Async Kwargs. Finish build_request_kwargs(): httpx.Client and AsyncClient accept the exact same arguments.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Choose httpx over requests for any new project, especially if async HTTP is plausible now or later

httpx's sync API is nearly identical to requests', so there's minimal cost to choosing it, while it avoids a potential future migration if async needs emerge.

Use the same httpx library (Client and AsyncClient) rather than mixing requests and aiohttp for sync/async needs

This keeps HTTP-calling code consistent across a codebase's sync and async portions, rather than requiring developers to context-switch between two entirely different libraries' APIs and conventions.

Frequent Bugs

THE BUG

Choosing requests for a new project that later needs async HTTP support, requiring a full migration to a different library (or maintaining two separate HTTP client libraries) rather than simply adding await to already-familiar httpx code.

THE FIX

Default to httpx for new projects, particularly if there is any plausible future need for async HTTP, avoiding a potential future library migration entirely.

Real-World Examples

A Client Class Supporting Both Sync and Async Usage Modes

A library needs to offer both a synchronous and an asynchronous API for the same underlying operations, since some consumers use it from plain scripts and others from an async web framework.

import httpx

class ApiClient:
    def __init__(self, base_url: str):
        self.base_url = base_url

    def get_user_sync(self, user_id: int) -> dict:
        with httpx.Client(timeout=5) as client:
            response = client.get(f"{self.base_url}/users/{user_id}")
            response.raise_for_status()
            return response.json()

    async def get_user_async(self, user_id: int) -> dict:
        async with httpx.AsyncClient(timeout=5) as client:
            response = await client.get(f"{self.base_url}/users/{user_id}")
            response.raise_for_status()
            return response.json()

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Calling an httpx.AsyncClient method without await, expecting it to behave like the synchronous Client's equivalent method, resulting in an unused coroutine object rather than an actual HTTP request.

# Wrong: coroutine created but never awaited, request never actually happens async def fetch(client, url): response = client.get(url) # missing await! # Correct async def fetch(client, url): response = await client.get(url)

The Solution //

Always await AsyncClient method calls (await client.get(url)), and ensure the calling function itself is declared async def.

Lesson Glossary

[01]httpx

A modern third-party Python HTTP client library offering both synchronous and native asynchronous APIs with HTTP/2 support.

Code Preview
// httpx context

[02]httpx.AsyncClient

httpx's asynchronous client class, mirroring the synchronous Client's method names as awaitable coroutines.

Code Preview
// httpx.AsyncClient context

[03]HTTP/2 multiplexing

A protocol feature allowing multiple concurrent requests and responses to be interleaved over a single TCP connection.

Code Preview
// HTTP/2 multiplexing context

[04]httpcore

The lower-level transport library httpx is built on, providing its HTTP/1.1 and HTTP/2 support.

Code Preview
// httpcore context

Continue Learning