You're then creating a Protocol for every single function that could rely on some duck typing.

Imagine one of your function just wants to move an iterator forward, and another just wants the current position. You're stuck with either requiring a full iterator interface when only part of it is needed or create one protocol for each function.

In day to day life that's dev time that doesn't come back as people are now spending time reading the protocol spaghetti instead of reading the function code.

I don't deny the usefulness of typing and interfaces in stuff like libraries and heavily used common components. But that's not most of your code in general.

For the collections case in particular, you can use the ABCs for collections that exist already[1]. There's probably in your use case that satisfies those. There's also similar things for the numeric tower[2]. SupportsGE/SupportsGT/etc should probably be in the stdlib but you can import them from typeshed like so

    from __future__ import annotations

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from _typeshed import SupportsGT
---

In the abstract sense though, most code in general can't work with anything that quack()s or it would be incorrect to. The flip method on an penguin's flipper in a hypothetical animallib would probably have different implications than the flip method in a hypothetical lightswitchlib.

Or less by analogy, adding two numbers is semantically different than adding two tuples/str/bytes or what have you. It makes sense to consider the domain modeling of the inputs rather than just the absolute minimum viable to make it past the runtime method checks.

But failing that, there's always just Any if you legitimately want to allow any input (but this is costly as it effectively disables type checking for that variable) and is potentially an indication of some other issue.

[1]: https://docs.python.org/3.14/library/collections.abc.html

[2]: https://docs.python.org/3/library/numbers.html

> You're then creating a Protocol for every single function that could rely on some duck typing.

No, you are creating a Protocol (the kind of Python type) for every protocol (the descriptive thing the type represents) that is relied on for which an appropriate Protocol doesn’t already exist. Most protocols are used in more than one place, and many common ones are predefined in the typing module in the standard library.