Last Updated: May 17, 2026
The static keyword attaches a member (field, method, property, or constructor) to the type itself rather than to any instance of it. There's exactly one copy, shared by everything in the program, and you reach it through the type name instead of an object. This lesson covers static fields, methods, properties, full static classes, the brief story of static constructors, the using static directive, and the design question of when to pick static over instance.
When you create two instances of a class, each instance gets its own set of fields and properties. Two carts have two Total values, two Owner names, two of everything. That's the default and it's what you usually want.
A static member breaks that pattern. There is one copy that belongs to the type. Whether you have zero, one, or a thousand instances of the class, the static member exists exactly once, sitting on the type itself.
Notice how TotalCartsCreated is accessed: Cart.TotalCartsCreated, not alice.TotalCartsCreated. The member lives on the type, so you reach it through the type. In fact, the compiler will warn you if you try to access a static member through an instance, because it implies a relationship that isn't there.
The instance fields Owner and Total belong to each cart. Three carts, three independent Total values. The static field TotalCartsCreated belongs to the Cart type itself. Three carts, still just one counter.
This memory picture is the foundation. Everything else about static is a consequence of it.
The diagram shows the type sitting at the top with one static field hanging off it. Each instance is a separate object with its own Owner and Total. The static field isn't duplicated per instance, it's part of the type's storage. In .NET terms, static fields live in a region of memory associated with the type's loaded metadata, not on the per-instance heap allocation.
A static field holds shared state for the entire type. Use cases include counters (how many orders have been processed), caches (a single lookup table reused across all callers), and configuration that doesn't change per instance (the default tax rate, the current store currency).
Each order increments the shared counter and adds to the shared revenue. No matter how many OrderSystem instances exist, there is one TotalOrdersPlaced and one TotalRevenue. The tax rate is a static default that any instance can read, and any code can update if the rate changes for a sale.
Two practical notes. First, public mutable static fields are dangerous in real code, because anyone can change them from anywhere. Prefer static properties with a private setter, or expose static fields as readonly. Second, static fields live for the lifetime of the application domain. They aren't garbage collected until the program ends, so don't stash large objects in them without thinking.
Cost: Mutating a static field from multiple threads requires explicit synchronization (lock, Interlocked.Increment, or a concurrent type). Two threads incrementing TotalOrdersPlaced at the same time can lose updates, because count++ is a read, an add, and a write, not one atomic step. Instance state has the same problem when shared, but static state is implicitly shared and the problem is easy to miss.
A safer version using a private static field and a public read-only property:
The field is private, so only the class can change it. Callers read it through the public property. The Interlocked.Increment call replaces _totalOrdersPlaced++ with a thread-safe single operation. You won't always need this level of care, but knowing it exists is the difference between code that works on a developer's laptop and code that works under load.
A static method doesn't operate on a particular instance. It receives all the data it needs through parameters and returns a result. You call it through the type name, with no new required.
CartCalculator is a holder for related functions. None of them need any per-instance state, so making them static is the right call. The BCL Math class is the canonical example: Math.Round, Math.Max, Math.Sqrt, all static, all stateless utilities.
Static methods cannot use this, because there's no instance for this to refer to. They also can't directly access instance fields or instance methods of the enclosing class. They can access other static members, and they can take instances as parameters and call methods on those.
Merge is static because it doesn't belong to one cart; it belongs to the operation of combining two. Putting it as alice.Merge(bob) would feel asymmetric, as if one cart owned the merge and the other was an argument. As a static method, both carts are inputs and the merged result is the output. Clean.
Static factory methods are a common pattern:
The constructor is private, so callers can't say new Product(...). Instead they go through Product.Regular(...) or Product.SaleItem(...), which are named static methods that document the intent. Compare to having a constructor with a bool onSale parameter; the static factory names read better and there's no risk of passing the boolean the wrong way around.
A static property is the property version of a static field. It belongs to the type, you access it through the type name, and it can have any combination of getter and setter (or init-only setter). Static properties are the right way to expose mutable type-level state, because they let you add validation and keep the underlying field private.
TaxRate has a full property with validation in the setter, so callers can't push it outside the legal range. CurrencySymbol is a simple auto-property with a default value. OpenCarts is read-only from outside (private setter), so callers have to go through the OpenCart and CloseCart methods to change it. That's the same pattern you'd use for instance properties; static just means it lives on the type.
Static auto-properties were added in C# 6 and they're the cleanest way to declare simple type-level data. If you don't need validation, you don't need the backing field.
A small but real example: a "current user" property that the rest of the app reads to know who's logged in.
Putting "the current user" on a static property like this is a popular shortcut in small apps. In larger ones it's a known anti-pattern (because it makes testing harder and assumes only one user per process), but knowing the pattern exists and what it looks like matters. Dependency injection replaces it in production code, but you'll still see it in older codebases and in tutorial-sized examples.
When every member of a class is static, you can mark the class itself static. The compiler then enforces a few things: you can't create instances of the class, you can't inherit from it, and every member must be static.
CartMath exists only to group related calculations. There's nothing meaningful about a CartMath instance, so the class is static. Try to write new CartMath() and the compiler stops you with CS0712 (cannot create an instance of the static class).
The rules at a glance:
| Restriction | Reason |
|---|---|
| All members must be static | The class has no instance state, so instance members would be unreachable |
Cannot be instantiated (new is disallowed) | There's no state to initialize |
| Cannot be inherited from | A subclass would need its own instance state or the same all-static rule |
Cannot inherit (always derives from object) | Static-ness must be a leaf property in the hierarchy |
| Can contain a static constructor | For one-time initialization of static fields |
| Cannot have instance constructors | There's no instance to construct |
The BCL is full of static classes: Math, Console, File, Directory, Path, Convert, Enumerable (the home of LINQ's extension methods). Each one is a namespace-like grouping of related functions with no instance to attach them to.
Cost: A static class is loaded once and lives for the lifetime of the application. The methods themselves have no per-call setup cost beyond a normal method call, so calling CartMath.AddTax(...) is no more expensive than calling an instance method. Don't pick instance over static for performance reasons; pick based on whether you actually have state.
A static constructor runs once, before any instance of the type is created and before any static member is first used. It's the place to set up static state that needs more than a simple field initializer can give you. The full mechanics of static constructors live in the Constructors lesson; here's the short version.
The static constructor doesn't run when the program starts. It runs the first time anything touches the CurrencyTable type, which is when ConvertToEur is called. Subsequent calls don't retrigger it. This lazy, run-once behavior is what makes static constructors useful for setup that's expensive, depends on configuration, or needs to throw on misconfiguration before any real work happens.
A few rules worth knowing without revisiting the dedicated lesson:
For the full picture (ordering with instance constructors, what happens if it throws, how it interacts with BeforeFieldInit), see the Constructors lesson in this section.
The using static directive (added in C# 6) imports the static members of a type so you can call them without the type qualifier. This is a call-site convenience, not a runtime difference.
Without the using static System.Math;, those calls would have to be Math.Round(...) and Math.Max(...). Without using static System.Console;, they'd be Console.WriteLine(...). The directive shaves off the type prefix.
When does this help? Code that uses one helper class heavily becomes cleaner. A formatting-heavy method that calls Math.Round, Math.Max, Math.Min, Math.Abs a dozen times reads better as Round, Max, Min, Abs. A test file that uses Assert.AreEqual repeatedly gets a similar boost.
When does it hurt? When the unqualified name is ambiguous or vague. Read is less clear than Console.Read. Min could be from Math, from a custom class, or from a LINQ method. If readers have to wonder where a function comes from, you lose more than you save. Use using static for one or two well-known helper types per file, not as a blanket cleanup.
Note: using static is for static members only. You can't use it to import instance members.
The decision between static and instance comes down to one question: does the operation depend on per-instance state? If yes, instance. If no, static. The rest is judgment about what the code is meant to express.
| Situation | Pick | Why |
|---|---|---|
| Operation has no state, just inputs and outputs | static | Nothing to attach to an instance |
| Helper functions grouped by topic (math, formatting, conversion) | static class | The class is a namespace, not an object |
| Counter or running total shared across all instances | static field/property | One copy is the whole point |
| Factory method that creates instances | static | The instance doesn't exist yet |
| Validation or comparison of two objects of the same type | static | Symmetric, neither object "owns" the operation |
| Per-object state (cart contents, order details, customer info) | instance | Each object's data is independent |
| Caching shared across the whole app | static field | The cache is a single shared resource |
| Configuration constants | static readonly or const | Doesn't change per instance |
A simple test: if you find yourself passing the same instance into a method over and over to do the same thing, you might be writing an instance method that wants to be static, or you might be writing a static method that wants to be an instance method. Walk through the code and see which way the dependency points. If the method really only needs one input, static is fine. If it pulls fields off the instance you pass in, it probably belongs on the class as a regular method.
A diagram of the decision flow:
What the diagram is showing. Start from the operation. If it touches per-instance state (reads a field, calls another instance method, mutates the object), it has to be an instance member. If it doesn't, ask whether the type itself has any instances that make sense. A Cart has instances and can host both static helpers and instance methods. A Math-style utility class has no instances at all, so the whole class is static.
A worked example. Consider these three methods on a hypothetical Order class. Which should be static and which shouldn't?
Option A is instance. It changes the state of this. Option B is static and stateless, a pure function from sequence number to an order ID string. Option C is the judgment call. It compares two orders, doesn't favor either one, and would feel awkward as a.IsLargerThan(b) because that implies a owns the comparison. Static reads better here.
Cost: Static methods can be a little faster to call than instance methods because they don't take an implicit this parameter, but the difference is invisible in normal code. Don't optimize a static-vs-instance choice for performance. Optimize it for what the method actually does and how it reads at the call site.
A realistic example combining static fields, static methods, a static factory, a static constructor, and an instance-level cart that uses all of them.
Walk through what each static is doing. The _nextCartId field guarantees unique IDs without coordinating between instances. The TaxRate property is store-wide configuration; changing it once affects every cart's total. The static constructor sets the initial values once. The Create factory hides the constructor and centralizes ID assignment. The instance members (Id, Owner, ItemPrices, AddItem, CalculateTotal) handle per-cart state and behavior. Each piece is on the right side of the static/instance line for what it does.
static attaches a member to the type itself, not to instances. There is one copy, shared by the whole program, accessed through the type name.this and can't directly access instance members of the enclosing type.static class is one where the compiler enforces that all members are static, no instances can be created, and no inheritance is allowed. Use it when the class is a namespace-like grouping of helpers (Math, Console, Path).using static SomeType; imports a type's static members so you can call them without the type prefix. Useful for one or two well-known helpers per file; overuse hurts readability.static class.