Python class methods, static methods, and plain instance methods look almost identical, which is why your cursor freezes the moment you start writing a method inside a class. Should it be a plain method, a @classmethod, or a @staticmethod? Almost every Python developer guesses wrong at least once. The good news: there is a 10 second rule that picks the right one every time.
“Choosing between instance, class, and static methods is choosing who needs to know what.”
Raymond Hettinger, PyCon talks
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Intermediate | Reading Time: 14 minutes
Think of a class like a pizza shop. An instance method works on one specific order (“add cheese to this pizza”). A class method works on the shop itself (“here is our standard margherita recipe”). A static method is a handy tool kept on the counter that anyone can grab (“is this a valid pizza size?”), it does not care about any single order or the shop. Same kitchen, three different jobs.
The only thing that really separates them is the first argument. Instance methods get self (the object). Class methods get cls (the class). Static methods get neither. That single difference decides what data the method can touch, and that is exactly what you use to choose. The rest of this post hands you a flowchart, a comparison table, and three real examples so the choice becomes automatic.
Table of Contents
The 10 Second Decision Flowchart
Before any code, here is the whole decision in one picture. Start at the top with your Python class and ask one question: what does this method need to touch? Follow the branch and you land on the right method type. Screenshot this and you will never guess again.
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
Read the diagram top to bottom. The pink box is your class. Below it sit the three method types, color coded. Under each one, the gray boxes tell you what it can and cannot reach. Instance methods (green) get self and can touch the object’s own data. Class methods (purple) get cls and can reach class level data and build new objects. Static methods (orange) get nothing, so they are plain helper functions that just happen to live in the class. What each one can reach is the decision rule. The table below turns that picture into a quick lookup.
The Three Method Types at a Glance
This is the table to bookmark for Python class methods, static methods, and instance methods side by side. Every row is a real difference you will hit in actual code, not marketing fluff.
| Feature | Instance Method | Class Method | Static Method |
|---|---|---|---|
| Decorator | None | @classmethod | @staticmethod |
| First arg | self (instance) | cls (class) | Nothing |
| Access instance? | Yes | No | No |
| Access class? | Yes (via self.__class__) | Yes | No |
| Call via instance? | Yes | Yes | Yes |
| Call via class? | No (need instance) | Yes | Yes |
| Use case | Operates on instance data | Factory methods, class state | Utility/helper functions |
Instance Methods: The Default
This is the one you already know. Any method whose first parameter is self is an instance method, and roughly nine out of ten methods you ever write will be this kind. The self is the specific object you called the method on, so the method can read and change that object’s own data. In the example below, a shopper named Anvi fills her cart, and every method call works on her cart alone.
📄 instance_methods.py: methods that work with self
class ShoppingCart:
def __init__(self, owner):
self.owner = owner
self.items = []
def add_item(self, name, price):
self.items.append({"name": name, "price": price})
return self # Enable method chaining
def total(self):
return sum(item["price"] for item in self.items)
def summary(self):
return f"{self.owner}'s cart: {len(self.items)} items, Rs.{self.total()}"
cart = ShoppingCart("Anvi")
cart.add_item("Python Book", 699).add_item("USB Cable", 299).add_item("Coffee", 150)
print(cart.summary())
▶ Output
Anvi's cart: 3 items, Rs.1148
What happened here: When you call cart.summary(), Python quietly passes cart in as self. You never type that argument yourself. Inside the method, self.items and self.owner are this cart’s own data, so the method always works on the right object. One neat trick is in add_item: it ends with return self, which hands the same cart back. That lets you chain calls in a single line, like cart.add_item(...).add_item(...).add_item(...), since each call returns the cart again. The shop analogy holds: an instance method is a worker handling this one order.
Class Methods: The Factory
Sometimes you want to build an object in more than one way. Maybe from a JSON (JavaScript Object Notation) string, maybe from a preset, maybe from a database row. You could write one giant __init__ that handles every case, but that gets ugly fast. A class method is the clean answer. It receives the class itself as cls, so it can call cls(...) to stamp out fresh objects. Think of it as the shop’s recipe card: “to make our standard config, mix these defaults.”
📄 class_methods.py: methods that work with cls
import json
class Config:
def __init__(self, db_host, db_port, debug):
self.db_host = db_host
self.db_port = db_port
self.debug = debug
@classmethod
def from_json(cls, json_string):
data = json.loads(json_string)
return cls(data["db_host"], data["db_port"], data["debug"])
@classmethod
def development(cls):
return cls("localhost", 5432, True)
@classmethod
def production(cls):
return cls("db.prod.internal", 5432, False)
def __repr__(self):
return f"Config({self.db_host}:{self.db_port}, debug={self.debug})"
dev = Config.development()
prod = Config.production()
from_data = Config.from_json('{"db_host": "staging.db", "db_port": 5433, "debug": true}')
print(dev)
print(prod)
print(from_data)
▶ Output
Config(localhost:5432, debug=True) Config(db.prod.internal:5432, debug=False) Config(staging.db:5433, debug=True)
What happened here: Each of these methods is decorated with @classmethod, so Python passes the class (Config) in as cls instead of an object. None of them has a self, so they cannot read instance data, and that is fine, there is no instance yet. Their whole job is to make one. Config.development() calls cls("localhost", 5432, True), which is just Config(...) with friendly defaults filled in. from_json does the same after parsing a JSON string. These alternative constructors are the number one reason class methods exist. Notice the JSON used true (lowercase) and Python correctly read it back as True.
Static Methods: The Utility
A static method is the odd one out. It gets no self and no cls. It is really just a normal function that you parked inside a class because it belongs there logically. Picture a calculator sitting on the shop counter: it does not know about any order or the shop, it just does its job when you press the buttons. Use this when a helper relates to the class by theme but does not need any object or class data to do its work.
📄 static_methods.py: methods that need no self or cls
class MathHelper:
@staticmethod
def is_even(n):
return n % 2 == 0
@staticmethod
def celsius_to_fahrenheit(c):
return c * 9/5 + 32
@staticmethod
def validate_email(email):
return "@" in email and "." in email.split("@")[1]
# Called straight on the class, no instance needed
print(MathHelper.is_even(42))
print(MathHelper.celsius_to_fahrenheit(37))
print(MathHelper.validate_email("aviraj@technoscripts.com"))
print(MathHelper.validate_email("bad-email"))
▶ Output
True 98.6 True False
What happened here: Notice none of these three methods has self or cls. They take only the data they need to do the calculation. celsius_to_fahrenheit(37) returns 98.6 straight from the formula, no object required. We grouped them under MathHelper so the name reads nicely (MathHelper.is_even(42)) and related helpers stay together. Here is the simple rule: if a method never uses self and never uses cls, it is begging to be a static method.
Here is the flowchart in words. When you are about to write a method, ask three questions in order and stop at the first “yes”:
- Does it use this object’s own data? Yes, then plain instance method (first arg
self, no decorator). - Does it build new objects or read class-level data? Yes, then
@classmethod(first argcls). - Neither? Then
@staticmethod, or honestly, ask yourself if it should just be a plain function in a module.
All Three in One Class
Theory sticks better when you see all three working together. Here is a tiny Pizza class that uses one of each: an instance method to price a specific pizza, two class methods that act as factories for popular combos, and a static method that validates a size without needing any pizza at all.
📄 all_three.py: a practical example using all three method types
class Pizza:
sizes = {"small": 199, "medium": 349, "large": 499}
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
# Instance method: works with this specific pizza
def price(self):
base = Pizza.sizes[self.size]
topping_cost = len(self.toppings) * 49
return base + topping_cost
# Class method: factory for common combos
@classmethod
def margherita(cls, size="medium"):
return cls(size, ["mozzarella", "basil", "tomato sauce"])
@classmethod
def paneer_tikka(cls, size="medium"):
return cls(size, ["mozzarella", "paneer tikka"])
# Static method: utility that does not need self or cls
@staticmethod
def is_valid_size(size):
return size in ("small", "medium", "large")
p1 = Pizza.margherita("large")
p2 = Pizza.paneer_tikka()
print(f"Margherita (L): Rs.{p1.price()}")
print(f"Paneer Tikka (M): Rs.{p2.price()}")
print(f"Is 'huge' valid? {Pizza.is_valid_size('huge')}")
▶ Output
Margherita (L): Rs.646 Paneer Tikka (M): Rs.447 Is 'huge' valid? False
What happened here: Watch how each method gets called. p1.price() runs on a specific pizza, so it needs self to know which toppings to count. A large margherita is the large base (499) plus three toppings at 49 each, so 499 + 147 = 646. Pizza.margherita("large") never touched an existing pizza; it built a brand new one through cls(...), which is the factory job. And Pizza.is_valid_size("huge") just answers a yes or no question with no pizza in sight, the perfect static method. One class, three jobs, three method types, exactly like the flowchart promised.
Real-World Scenarios
Forget pizzas for a second. Here is where Python class methods and their two siblings show up in code you will actually ship:
- Loading a user from a database row calls for a class method. You write
User.from_row(row)that parses the row and returnscls(...). The raw__init__stays clean while the messy parsing lives in its own named constructor. - Updating an order’s status is an instance method.
order.mark_shipped()changesself.statuson that one order. It is about a single object, soselfis exactly what you need. - Checking if a phone number looks valid is a static method.
Customer.is_valid_phone("9876543210")needs no customer and no class data, just the string. It lives onCustomeronly because that is where you go looking for it.
Common Mistakes
Mistake 1: Using @staticmethod when you need @classmethod
📄 mistake_wrong_decorator.py
# Two versions of the SAME factory, shown side by side for contrast
class AnimalBad:
# BAD: hardcodes the class name, so subclasses cannot reuse it
@staticmethod
def create_dog():
return AnimalBad("Dog")
def __init__(self, species):
self.species = species
class AnimalGood:
# GOOD: cls adapts automatically, even for subclasses
@classmethod
def create_dog(cls):
return cls("Dog")
def __init__(self, species):
self.species = species
Think of a franchise recipe card. A card that says “prepare this at the Pune branch” is useless the day you open a branch in Nagpur. A card that says “prepare this at whichever branch you are standing in” works everywhere, and that is exactly what cls gives you. The @staticmethod version bakes in the name AnimalBad. If you later make a Puppy(AnimalBad) subclass and call Puppy.create_dog(), you still get an AnimalBad, not a Puppy, which is almost never what you want. The @classmethod version uses cls, so Puppy.create_dog() correctly returns a Puppy. Rule of thumb: if a method calls the constructor, it should be a class method.
Mistake 2: Making everything a static method
If your class is nothing but static methods, that is a smell. You probably do not need a class at all, just a module with plain functions. Classes earn their keep by bundling data with the behavior that acts on it. No data, no reason for a class.
Decision Summary
The whole post in five lines you can keep next to your keyboard:
- Use an instance method when the work touches one object’s data. This is your default and most methods fit here.
- Use
@classmethodfor factory methods and alternative constructors (anything that callscls(...)to build an object). - Use
@staticmethodfor helper functions that belong with the class by theme but need noselforcls. - Never reach for
@staticmethodwhen the method builds objects. That is a class method’s job, and static breaks with inheritance. - Stop and rethink if a class is all static methods. A module of plain functions is simpler and clearer.
Practice Exercises
- Build a
Temperatureclass. Give it an instance method that returns the value in Fahrenheit, a class methodfrom_fahrenheit(cls, f)that builds an object from a Fahrenheit reading, and a static methodis_freezing(celsius)that returnsTruebelow zero. One class, all three method types. - Spot the wrong decorator. Take a class where a factory method is marked
@staticmethodand hardcodes the class name. Make a subclass, prove it returns the wrong type, then switch it to@classmethodwithclsand prove it now adapts. - Refactor a junk-drawer class. Find or write a class that is nothing but static methods. Decide which ones truly belong together, then move the rest into a plain module of functions. Notice how much simpler the code reads.
Conclusion
Python gives you three method types: instance methods for working with object data, class methods for factory patterns and class-level operations, and static methods for utilities that belong logically to the class. The whole choice comes down to one question, what does this method need to touch: the instance, the class, or neither? Answer that and the decorator picks itself.
With Python class methods, static methods, and instance methods sorted, you are ready for inheritance. In the inheritance tutorial, you will learn how to create class hierarchies where child classes inherit and extend behavior from parent classes. And if you want the full roadmap from Python basics all the way to AI and ML, browse the Python + AI/ML tutorial series home.
Frequently Asked Questions
What is the difference between @classmethod and @staticmethod in Python?
@classmethod receives the class as cls and can access class attributes and create instances. @staticmethod receives nothing, so it is just a regular function living inside a class. Use classmethod for factory patterns, staticmethod for utilities.
When should I use @classmethod in Python?
Use @classmethod when you need to: create instances from alternative data sources (factory methods like from_json, from_csv), create preset configurations (Config.development()), or access/modify class-level attributes.
Can I call a class method on an instance?
Yes. obj.class_method() works fine, and Python still passes the class, not the instance. But calling it on the class (MyClass.class_method()) is clearer and preferred.
When should I use @staticmethod vs a standalone function?
Use @staticmethod when the function is logically related to the class (like a validator or converter). Use a standalone function when it has nothing to do with the class. If you’re unsure, standalone function is usually the simpler choice.
What does self refer to in a Python method?
self refers to the specific instance the method was called on. When you call cart.total(), Python passes cart as self. That’s how the method knows which cart’s items to sum up.
Interview Questions on Python Class Methods
Scenario questions, not trivia: this is the form this topic takes in a real interview.
Q: A teammate adds a from_dict factory to a base Model class, but when your colleague Anvay calls User.from_dict(data) on his User(Model) subclass, he gets a Model back instead of a User. What went wrong?
The factory is almost certainly hardcoding the class name, either return Model(...) inside a @staticmethod or even inside a @classmethod that ignores cls. Because the class name is baked in, every subclass gets a Model. The fix is to make it a @classmethod that returns cls(...): when called as User.from_dict(data), Python passes User in as cls, so the right type comes out automatically.
Q: You forget the @classmethod decorator on def development(cls) in a Config class. What happens when someone calls Config.development()?
Without the decorator it is a plain instance method, so Python does not pass the class automatically. Calling Config.development() on the class raises TypeError: development() missing 1 required positional argument: 'cls' because nothing fills that first parameter. Worse, calling it on an instance passes the instance as cls, and cls("localhost", ...) then tries to call an object like a function and fails. The decorator is what makes Python inject the class.
Q: During code review you find a class with 12 methods and every single one is @staticmethod. What feedback do you give?
The class holds no state, so it is not earning its keep: it is just a namespace. In Python the idiomatic namespace is a module, so the cleaner refactor is to move those functions into a module like validators.py and import them directly. That removes a level of indirection, shortens call sites, and makes the functions easier to test and reuse. A class makes sense only if some of those methods start sharing instance or class data.
Q: Why can a class method not read or modify instance attributes?
Because it never receives an instance. Python hands it only cls, the class object, and a class can have zero, one, or a million instances at call time, so there is no single self it could refer to. It can read and write class-level attributes through cls, and it can create a new instance with cls(...), but it cannot touch the attributes of any existing object unless you pass that object in explicitly as a normal argument.
Q: Can a subclass override a @classmethod or @staticmethod, and does the parent’s factory pick up the override?
Yes, both can be overridden just like normal methods: define a method with the same name in the subclass, keeping the same decorator. And yes, lookups stay polymorphic. If a parent class method calls another method via cls.validate(), and the subclass overrides validate, calling the factory on the subclass runs the subclass version, because cls is the subclass. This is what makes classmethod factories play well with inheritance.
Q: What does @classmethod actually do under the hood when Python sees it?
It wraps the function in a classmethod descriptor object. When you access the method through the class or an instance, the descriptor’s binding logic returns a bound method whose first argument is already set to the class, which is why you never pass cls yourself. @staticmethod is a descriptor too, one that returns the raw function with no binding at all. You do not need descriptor internals daily, but naming them in an interview shows you know decorators here are not magic syntax, they are ordinary objects implementing __get__.
Want more? the official Python documentation documents everything this post could not fit.
Related Posts
Previous: Python: Constructors __init__ & Instance Variables
Next: Python Inheritance: Single, Multi-level, Hierarchical
Series Home: Python + AI/ML Tutorial Series

No comment