Python
static variables
programming
functions
coding tips

What is the Python equivalent of static variables inside a function?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Python does not have a direct static keyword for function-local variables the way C or C++ does. Even so, Python offers several ways to preserve state across function calls. The right choice depends on whether you want the state to be visible, private, or split across multiple independent instances.

Function Attributes Are the Closest Match

Functions are regular objects in Python, so you can attach attributes to them. That makes function attributes the closest conceptual equivalent to a static variable tied to one function.

python
1def counter():
2    counter.calls += 1
3    return counter.calls
4
5
6counter.calls = 0
7
8print(counter())
9print(counter())
10print(counter())

The state lives on the function object itself. That makes the behavior easy to inspect in a debugger and easy to reset in tests.

Closures Keep the State Private

If you want persistent state but do not want to expose it as a public attribute, a closure is usually cleaner.

python
1def make_counter():
2    calls = 0
3
4    def counter():
5        nonlocal calls
6        calls += 1
7        return calls
8
9    return counter
10
11
12c1 = make_counter()
13c2 = make_counter()
14
15print(c1(), c1(), c1())
16print(c2(), c2())

Each call to make_counter() creates a new private state cell. That makes closures a better fit when you need multiple independent counters rather than one global function-level counter.

Classes Are Often the Clearest Design

Once the preserved state grows beyond a trivial counter or cache, a class is often the most readable solution.

python
1class Counter:
2    def __init__(self):
3        self.calls = 0
4
5    def __call__(self):
6        self.calls += 1
7        return self.calls
8
9
10counter = Counter()
11print(counter())
12print(counter())

A class makes the state explicit, testable, and extensible. If you anticipate more behavior later, starting with a class is often simpler than hiding state inside a function trick.

Mutable Default Arguments Can Persist State

Default argument expressions are evaluated once, so a mutable default can also behave like persistent state.

python
1def counter(state={"calls": 0}):
2    state["calls"] += 1
3    return state["calls"]
4
5
6print(counter())
7print(counter())
8print(counter())

This works, but it is easy to misread. Many Python programmers associate mutable defaults with bugs because accidental state retention is a common mistake. Use this pattern only when the persistence is intentional and obvious to readers.

Use Standard Tools for Common Stateful Patterns

Sometimes what looks like a static variable problem is really a caching problem. In those cases, the standard library often provides a clearer answer.

python
1from functools import lru_cache
2
3@lru_cache(maxsize=None)
4def fib(n):
5    if n < 2:
6        return n
7    return fib(n - 1) + fib(n - 2)
8
9
10print(fib(20))

This is usually better than manually storing cache data on the function when the real goal is memoization.

Common Pitfalls

The biggest mistake is hiding shared mutable state where readers do not expect it. A function that looks pure but remembers old calls can surprise other developers and make tests interdependent.

Another issue is choosing a clever function-level technique when a small class would be clearer. If the state has more than one field or needs reset logic, explicit objects usually win.

Mutable default arguments are especially risky because they are easy to use accidentally. If you use them on purpose, make that intent obvious in the function name, documentation, or surrounding code.

Finally, remember that all of these patterns create shared state unless you deliberately create separate instances. In concurrent code, that can introduce race conditions or hidden coupling between callers.

Summary

  • Python has no direct function-local static keyword.
  • Function attributes are the closest simple equivalent.
  • Closures are better when you want private state or multiple independent instances.
  • Classes are usually the clearest choice once the state becomes non-trivial.
  • Use purpose-built tools such as lru_cache when the real need is caching rather than generic persistence.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.