Back to Blog
Python

Python type vs isinstance: When to Use Each

Understand the practical difference between Python's type() and isinstance(), including inheritance behavior, protocol checks, and when to use each.

Pythontype checkingisinstanceduck typinginheritance
Illustration comparing Python type() and isinstance() functions with inheritance hierarchy.

In Python, the choice between type() and isinstance() affects how you validate types at runtime. The difference becomes critical when inheritance or duck typing is involved. This article explains the practical difference between the two, with code examples and guidance for production code.

The Core Difference

type() returns the exact class of an object, while isinstance() checks whether an object is an instance of a given class or a subclass of it. This distinction matters whenever inheritance is involved. For example, type(instance) == SomeClass is True only when the object's class is exactly SomeClass. isinstance(instance, SomeClass) is True when the object is an instance of SomeClass or any of its subclasses.

How type() Works

type() is a built-in function that returns the type object of its argument. When called with one argument, it gives you the exact class. When called with three arguments, it creates a new class, but that usage is unrelated to type checking. For type checks, you typically compare the result with a class using == or is. Because type() returns the exact class, it does not consider inheritance.

class Animal: pass class Dog(Animal): pass d = Dog() print(type(d) == Dog) # True print(type(d) == Animal) # False

The second comparison is False even though Dog is a subclass of Animal. This behavior is often surprising to developers who expect a subclass to also match the base class. That expectation is reasonable in a language with polymorphism, but type() does not provide it.

How isinstance() Works

isinstance() takes an object and a class or a tuple of classes. It returns True if the object is an instance of any of the given classes or of a subclass of them. The check uses the object's type and walks up the inheritance chain.

class Animal: pass class Dog(Animal): pass d = Dog() print(isinstance(d, Dog)) # True print(isinstance(d, Animal)) # True

This makes isinstance() the natural choice when you need to accept subclasses or when you are working with an interface or abstract base class. It also supports multiple types in a single call, such as isinstance(value, (int, float)).

Inheritance and Subclass Behavior

The key difference becomes visible in a class hierarchy. Suppose you have a base class Shape and a subclass Circle. If you want to check whether an object is a Circle or any other shape, isinstance() is the only straightforward way. With type(), you would need to manually inspect the class hierarchy or use issubclass(), which is more verbose and error-prone.

class Shape: pass class Circle(Shape): pass c = Circle() print(type(c) is Circle) # True print(type(c) is Shape) # False print(isinstance(c, Shape)) # True

In a polymorphic system, isinstance() expresses the intent more clearly. It also handles objects created by dynamically created classes and classes registered as virtual subclasses of an abstract base class.

Practical Usage: When to Use Each

Use type() when you need an exact match and you explicitly do not want to accept subclasses. This is rare, but it can be useful for strict type guards that should reject any derived class.

Use isinstance() for almost every other type check. It is the idiomatic way to validate input in functions, especially when you are accepting a base class or an interface. It also works with abstract base classes and, when decorated with @runtime_checkable, protocol classes.

def process_number(value): if not isinstance(value, (int, float)): raise TypeError("Expected a number") return value * 2

The tuple form of isinstance() is a concise way to accept multiple types without repeating the check.

Performance and Runtime Cost

Both functions are implemented in C and are fast. isinstance() may have to traverse the MRO (method resolution order) of the object's class to check all base classes. In typical code, that traversal is short and the overhead is negligible. type() returns the exact class object, so checking for an exact class is also cheap. The difference is rarely a bottleneck and should not drive the decision.

The more important consideration is what you do after the check. If you use type() and then manually handle subclasses, you add complexity and risk. If you use isinstance(), you get the correct behavior without extra code. Prematurely optimizing type checks is usually a mistake; correctness and maintainability matter more.

Edge Cases and Duck Typing

Python's philosophy encourages duck typing: if an object behaves like a duck, treat it as a duck. Type checks should be used sparingly, and when they are necessary, isinstance() aligns better with that philosophy because it can accept an object that implements the required interface. This includes structural checks through typing.Protocol, but only when the protocol is explicitly marked as runtime-checkable.

from typing import Protocol, runtime_checkable @runtime_checkable class Named(Protocol): name: str def greet(obj): if isinstance(obj, Named): return f"Hello, {obj.name}" raise TypeError("Expected a Named object")

Here, isinstance() works with a runtime-checkable protocol class, while type() would require an exact class match. Keep in mind that runtime protocol checks are opt-in and generally verify the presence of the expected members, not the full type signature.

Maintainability Considerations

Using isinstance() consistently makes your code easier to extend. If you introduce a new subclass later, existing checks continue to work without modification. With type(), you would need to update every exact comparison to include the new subclass. In a large codebase, this can lead to subtle bugs where a valid subclass is rejected.

When you are writing public APIs, prefer isinstance() with abstract base classes or protocols. This gives callers flexibility while still providing clear error messages. Reserve type() for internal checks where you have full control over the class hierarchy and you explicitly need to reject subclasses.

type() vs isinstance() in Python: Practical Usage and Code Examples | RYUSLOG DEV