programming
function parameters
class types
software development
coding techniques

How to pass a class type as a function parameter

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Passing a class type into a function means the function receives the class itself rather than an already-created object. That pattern is useful when the function needs to construct an instance later, choose among implementations, or inspect type metadata.

In Python, Classes Are Ordinary Values

In Python, classes are first-class objects. You can store them in variables, return them from functions, and pass them as arguments just like strings or callables.

python
1class EmailSender:
2    def __init__(self, host: str):
3        self.host = host
4
5    def send(self, message: str) -> None:
6        print(f"Email via {self.host}: {message}")
7
8
9def build_sender(sender_cls, host: str):
10    return sender_cls(host)
11
12
13sender = build_sender(EmailSender, "smtp.internal")
14sender.send("hello")

Here, EmailSender is the function argument. The function decides when to instantiate it.

Add Type Hints For Clarity

If several related classes are valid, type hints make the contract easier to understand. In modern Python, type[BaseClass] expresses "a class object whose instances are subclasses of this base type."

python
1class BaseSender:
2    def send(self, message: str) -> None:
3        raise NotImplementedError
4
5
6class SmsSender(BaseSender):
7    def __init__(self, host: str):
8        self.host = host
9
10    def send(self, message: str) -> None:
11        print(f"SMS via {self.host}: {message}")
12
13
14def build_sender(sender_cls: type[BaseSender], host: str) -> BaseSender:
15    return sender_cls(host)

That annotation helps both readers and static analysis tools. It tells the caller that the function expects a class, not an instance, and that the returned object will conform to the BaseSender interface.

This Pattern Is Common In Factories And Plugins

Class parameters are especially useful in factory-style code. The caller chooses the implementation, while the shared function handles the construction and later workflow.

python
1class JsonParser:
2    def __init__(self, source: str):
3        self.source = source
4
5    def parse(self):
6        return {"kind": "json", "source": self.source}
7
8
9class CsvParser:
10    def __init__(self, source: str):
11        self.source = source
12
13    def parse(self):
14        return [["kind", "csv"], ["source", self.source]]
15
16
17def run_parser(parser_cls, source: str):
18    parser = parser_cls(source)
19    return parser.parse()
20
21
22print(run_parser(JsonParser, "input.json"))
23print(run_parser(CsvParser, "input.csv"))

This keeps the caller in control of the concrete behavior without duplicating the surrounding orchestration logic.

Know When To Pass An Instance Instead

Not every API should accept a class type. If the function needs an object that is already configured and holding state, pass the instance instead.

python
def send_now(sender, message: str) -> None:
    sender.send(message)

That function assumes the object already exists. It does not care how the object was created.

If the function needs to control construction time or constructor arguments, then a class parameter makes sense:

python
def send_later(sender_cls, host: str, message: str) -> None:
    sender = sender_cls(host)
    sender.send(message)

This distinction matters because an unclear API forces callers to guess whether they should pass EmailSender or EmailSender("smtp.internal").

The Same Idea Exists In Other Languages

Statically typed languages express the same pattern with different syntax. In Java, a function may accept Class<T>. In C#, the API may accept Type or use a generic type parameter with constructor constraints. The underlying design question is still identical: do you need an already-built object, or do you need the recipe for constructing one?

Once you frame the problem that way, the correct function signature usually becomes obvious.

Common Pitfalls

  • Passing an instance when the function really needs the class and constructor behavior.
  • Passing a class when the function actually needs a fully configured object.
  • Forgetting to document constructor requirements for the accepted classes.
  • Omitting base-class or protocol constraints, which makes misuse easier.
  • Hiding too much work inside class-based factories so callers cannot tell what gets instantiated.

Summary

  • Passing a class type means the function receives the class object itself.
  • This is useful for factories, plugins, and delayed construction.
  • In Python, type[BaseClass] is the clearest annotation when subclasses are expected.
  • Choose between class parameters and instances based on whether the function needs construction control or existing state.

Course illustration
Course illustration

All Rights Reserved.