Methods
Reviewed & published by Brayan K
By the end of this lesson you'll be able to package code into reusable, named methods — passing in parameters, handing back results with return, giving parameters sensible defaults, and overloading one name for several jobs. This is how real programs stay organised instead of becoming one giant block of code.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Define methods with parameters and call them with arguments
- Return a value with return, or use void for actions with no result
- Use optional (default), named, and params arguments
- Hand back multiple values with out parameters (the TryParse pattern)
- Overload one method name for different parameter signatures
- Write expression-bodied methods and understand variable scope
💡 Real-World Analogy
A method is like a kitchen appliance. A blender takes ingredients (parameters), processes them, and hands back a smoothie (the return value) — and you never have to know how the motor works inside (that's abstraction). The label on the button is the method name; what you drop in is the arguments. Overloading is a multi-function cooker: one name on the front, but it bakes, steams, or pressure-cooks depending on which settings (parameter types) you give it. Once you've built an appliance, you press its button as many times as you like — that's the whole point of a method.
📊 Method Syntax Reference
| Feature | Syntax | Meaning |
|---|---|---|
| Void method | static void Name() | Does something, returns nothing |
| Return value | static int Name() | Must return an int |
| Expression body | static int F() => expr; | One-liner shorthand for return |
| Optional param | void F(int x = 5) | Uses 5 if the caller omits it |
| Params array | void F(params int[] n) | Any number of arguments |
| Out parameter | bool F(out int r) | Hands back an extra value |
| Named args | F(name: "Bo", age: 9) | Pass by name, any order |
Parameter = the placeholder named in the method's definition. Argument = the actual value you pass in when you call it. Same slot, two words — they trip everyone up at first.
1. Defining & Calling Methods
A method has a return type (what it gives back), a name, and a list of parameters (its inputs). Use void when it just does something with no result to report; use a type like int, string or double when it should hand a value back with return. Simple one-liners can use the expression-bodied form => instead of { return ...; }. Read this worked example first, run it, then you'll finish one yourself.
using System;
class Program
{
// A void method DOES something but returns nothing.
// Shape: static void Name(parameters) { ... }
static void Greet(string name)
{
Console.WriteLine($"Hello, {name}! 👋"); // prints, returns nothing
}
// A method with a RETURN TYPE (int) must hand a value back.
// int Add(int a, int b)
// |--| |-| |--------|
// return name parameters (the inputs)
static int Add(int a, int b)
{
return a + b; // 'return' sends the answer to the caller
}
// Expression-bodied method — '=>' is shorthand for { return ...; }
static double CircleArea(double radius) => Math.PI * radius * radius;
// A method can return from several places (the first one wins).
static string GetGrade(int score)
{
if (score >= 90) return "A"; // returns here for 90+
if (score >= 80) return "B";
if (score >= 70) return "C";
return "F"; // fallback if none matched
}
static void Main()
{
// Calling = writing the method's name with arguments in ( ).
Greet("Alice"); // Hello, Alice! 👋
Greet("Bob"); // Hello, Bob! 👋
int sum = Add(5, 3); // capture the returned value
Console.WriteLine($"5 + 3 = {sum}"); // 5 + 3 = 8
double area = CircleArea(5);
Console.WriteLine($"Circle area (r=5): {area:F2}"); // 78.54
Console.WriteLine($"Score 85 → Grade {GetGrade(85)}"); // Grade B
Console.WriteLine($"Score 92 → Grade {GetGrade(92)}"); // Grade A
}
}
// ✅ Expected output:
// Hello, Alice! 👋
// Hello, Bob! 👋
// 5 + 3 = 8
// Circle area (r=5): 78.54
// Score 85 → Grade B
// Score 92 → Grade AYour turn. The method below is meant to return the area of a rectangle, but the return type and the return expression are blank. Fill in the two ___ blanks using the hints, then run it.
using System;
class Program
{
// 🎯 YOUR TURN — fill in the blanks marked with ___ then run it.
// Goal: finish a method that RETURNS the area of a rectangle.
// 1) The method must hand back a number, so its return type is int.
static ___ RectangleArea(int width, int height) // 👉 return type: int
{
// 2) Multiply the two parameters and send the answer back.
return ___; // 👉 the expression: width * height
}
static void Main()
{
// This line stores whatever RectangleArea returns:
int area = RectangleArea(4, 5);
Console.WriteLine($"4 x 5 rectangle area = {area}");
// ✅ Expected output:
// 4 x 5 rectangle area = 20
}
}2. Parameters — Optional, Named, Params & Out
Optional parameters carry a default value, so callers can leave them off (they must come after all required parameters). Named arguments let you pass values by name in any order, which makes calls self-documenting. params accepts any number of arguments as an array. And out lets a method hand back more than one value at once — it's exactly how int.TryParse returns both a success flag and the parsed number.
using System;
class Program
{
// OPTIONAL parameters have a default. They MUST come after required ones.
static void PrintMessage(string msg, int times = 1, string prefix = "→")
{
for (int i = 0; i < times; i++)
Console.WriteLine($"{prefix} {msg}"); // repeats 'times' lines
}
// params — accept ANY number of arguments as an array.
static int Sum(params int[] numbers)
{
int total = 0;
foreach (int n in numbers) total += n;
return total;
}
// out — let a method hand back MORE than one value.
// (This is exactly how int.TryParse works.)
static bool TryDivide(int a, int b, out double result)
{
if (b == 0)
{
result = 0; // out params MUST be assigned before returning
return false; // signal "it didn't work"
}
result = (double)a / b;
return true; // signal "success" — result now holds the answer
}
static void Main()
{
PrintMessage("Hello"); // uses defaults -> "→ Hello"
PrintMessage("Important!", 3, "⚠️"); // 3 lines, custom prefix
// NAMED arguments — pass by name, in any order, self-documenting.
PrintMessage(times: 2, msg: "Named!", prefix: "✅");
// params — pass as many values as you like:
Console.WriteLine($"Sum: {Sum(1, 2, 3)}"); // Sum: 6
Console.WriteLine($"Sum: {Sum(10, 20, 30, 40)}"); // Sum: 100
// out — read the bool, then use the result it filled in.
if (TryDivide(10, 3, out double answer))
Console.WriteLine($"10 / 3 = {answer:F2}"); // 10 / 3 = 3.33
if (!TryDivide(10, 0, out _)) // '_' = discard
Console.WriteLine("Cannot divide by zero!"); // this runs
}
}
// ✅ Expected output:
// → Hello
// ⚠️ Important!
// ⚠️ Important!
// ⚠️ Important!
// ✅ Named!
// ✅ Named!
// Sum: 6
// Sum: 100
// 10 / 3 = 3.33
// Cannot divide by zero!Now you try. The method below greets someone, with a greeting that should default to "Hello". Fill in the default value and the two calls — one using the default, one overriding it.
using System;
class Program
{
// 🎯 YOUR TURN — fill in the blanks marked with ___ then run it.
// Goal: greet someone, with an OPTIONAL greeting that defaults to "Hello".
// 1) Give 'greeting' a default value of "Hello" so callers can skip it.
static void Welcome(string name, string greeting = ___) // 👉 default: "Hello"
{
Console.WriteLine($"{greeting}, {name}!");
}
static void Main()
{
// 2) Call Welcome with ONLY a name — it should use the default greeting.
Welcome(___); // 👉 just a name, e.g. "Sam"
// 3) Call Welcome and OVERRIDE the greeting with "Welcome".
Welcome("Mia", ___); // 👉 the greeting text: "Welcome"
// ✅ Expected output:
// Hello, Sam!
// Welcome, Mia!
}
}3. Overloading, Scope & Recursion
Method overloading means giving several methods the same name but different parameter types or counts — the compiler picks the right one from the arguments you pass (note: the return type alone can't tell two overloads apart; the parameters must differ). Scope is where a name is visible: a variable declared inside a method is local to it and disappears when the method ends. Recursion is a method that calls itself — handy for problems that shrink toward a base case that stops the recursion.
using System;
class Program
{
// OVERLOADING — same name, DIFFERENT parameters. The compiler
// picks the version that matches the arguments you pass.
static int Multiply(int a, int b) => a * b;
static double Multiply(double a, double b) => a * b;
static int Multiply(int a, int b, int c) => a * b * c;
// SCOPE: 'a', 'b', 'c' above are LOCAL — they only exist inside
// their own method. A variable made in Main can't be seen here.
// RECURSION — a method that calls itself. It needs a BASE CASE to stop.
static int Factorial(int n)
{
if (n <= 1) return 1; // base case — stops the recursion
return n * Factorial(n - 1); // recursive case — n shrinks each call
}
static void Main()
{
// Each call matches a DIFFERENT overload by its arguments:
Console.WriteLine($"int: {Multiply(5, 3)}"); // int: 15
Console.WriteLine($"double: {Multiply(2.5, 4.0)}"); // double: 10
Console.WriteLine($"3 args: {Multiply(2, 3, 4)}"); // 3 args: 24
// Recursion: 5! = 5*4*3*2*1
Console.WriteLine($"5! = {Factorial(5)}"); // 5! = 120
// 'total' lives only inside Main — that's its scope.
int total = Multiply(2, 3) + Multiply(4, 5);
Console.WriteLine($"Combined: {total}"); // Combined: 26
}
}
// ✅ Expected output:
// int: 15
// double: 10
// 3 args: 24
// 5! = 120
// Combined: 26🔎 Deep Dive: passing by value vs ref / out
By default a method gets a copy of the value you pass (this is "pass by value"). Changing the parameter inside the method does not change the caller's variable. To let a method change the caller's variable, use ref (in and out) or out (out only — the method must assign it before returning).
static void AddOne(int n) { n++; } // works on a COPY
static void AddOneRef(ref int n) { n++; } // works on the ORIGINAL
int x = 5;
AddOne(x); // x is still 5 (the copy was changed, not x)
AddOneRef(ref x); // x is now 6 (ref reached back to the original)Use out when a method needs to produce an extra result (like TryParse). Use ref when it needs to read and update an existing value. Most of the time, prefer returning a value (or a tuple) over ref — it's clearer.
Putting It Together: a Tiny Shopping Cart
Here's a small program that uses everything from this lesson at once — a params method to total any number of prices, an expression-bodied method with an optional tax rate, a void printer, and a named argument to override the default. You understand every line now.
using System.Globalization;
using System;
class Program
{
// === Tiny shopping cart — every method here uses this lesson's skills ===
// Returns a value; uses an OPTIONAL tax rate parameter.
static decimal WithTax(decimal amount, decimal rate = 0.20m)
=> amount + amount * rate; // expression-bodied, returns decimal
// params — sum any number of item prices into a subtotal.
static decimal Subtotal(params decimal[] prices)
{
decimal sum = 0;
foreach (decimal p in prices) sum += p;
return sum;
}
// void — just prints; no value handed back.
static void PrintLine(string label, decimal value)
=> Console.WriteLine($"{label,-10} {value:C}");
static void Main()
{
// Currency formatting follows the thread's culture: {value:C} prints
// £ in London, $ in Boston and € in Paris. Pin it when the output has
// to be the same everywhere — as it does on a page that shows you the
// result.
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
decimal sub = Subtotal(9.99m, 4.50m, 14.00m); // 28.49
decimal total = WithTax(sub); // default 20% tax applied
PrintLine("Subtotal:", sub); // Subtotal: £28.49
PrintLine("Total:", total); // Total: £34.19
// Override the optional rate using a named argument (5% tax):
PrintLine("If 5%:", WithTax(sub, rate: 0.05m)); // If 5%: £29.91
}
}
// ✅ Expected output:
// Subtotal: £28.49
// Total: £34.19
// If 5%: £29.91Notice how each method does one job and has a name that says what it does. That's the habit to build: small, well-named methods read like a description of the program.
Pro Tips
- 💡 One method, one job: if a method is over ~20 lines or does several things, split it. Small methods are easier to name, test, and reuse.
- 💡 Name methods as verbs: CalculateTotal, IsValid, GetUser — the name should say what it does or returns.
- 💡 Prefer returning a value (or tuple) over out: static (int min, int max) Range(int[] a) reads more cleanly than two out parameters.
- 💡 Use expression bodies for one-liners: static bool IsEven(int n) => n % 2 == 0; is clean and clear.
- 💡 Keep parameter lists short: more than three or four parameters usually means the data belongs together in a class or record (next lesson).
Common Errors (and the fix)
- "CS0161: not all code paths return a value": a method with a return type must return on every path. If you have if (x) return a; with no final return, add a fallback return after the if.
- "CS1501: No overload for method 'X' takes N arguments": you called the method with the wrong number of arguments. Check the definition — e.g. Add(5) when Add(int a, int b) needs two.
- "CS0029: Cannot implicitly convert type 'string' to 'int'": you returned the wrong type. A static int GetName() that returns text won't compile — match the return type to what you actually return (here, change it to string).
- "CS0127: Since 'X' returns void, a return keyword must not be followed by an object expression": a void method tried to return a value. Either drop the value (return; on its own is fine for an early exit) or change the method's return type.
- "CS1737: Optional parameters must appear after all required parameters": you put a defaulted parameter before a required one. Reorder so all = default parameters come last.
- Overloading by return type only: two methods with the same name and parameters but different return types won't compile — the parameters must differ.
📋 Quick Reference
| Task | Code | Notes |
|---|---|---|
| Action, no result | static void Log(string m) | No return value |
| Return a value | static int Add(int a, int b) | return a + b; |
| One-liner | static int Sq(int n) => n * n; | Expression body |
| Default value | void F(int x = 0) | Optional param (last) |
| Call by name | F(x: 5) | Named argument |
| Many arguments | int Sum(params int[] n) | Sum(1, 2, 3) |
| Extra output | bool TryX(out int r) | Assign r before return |
| Overload | F(int) / F(double) | Params must differ |
Frequently Asked Questions
Q: What's the difference between a parameter and an argument?
A parameter is the named placeholder in the method's definition — int a in Add(int a, int b). An argument is the real value you pass when you call it — the 5 in Add(5, 3). Definition side = parameter; call side = argument.
Q: When should I use void vs a return type?
Use void when the method's job is to do something (print, save, send) and there's no answer to hand back. Use a return type when the caller needs a result — a total, a grade, a true/false. If you find yourself printing inside a method and the caller also wants the value, return it instead and let the caller print.
Q: What's the difference between out and ref?
Both let a method change the caller's variable. out is "output only" — the method must assign it before returning, and the caller doesn't need to set it first (used by TryParse). ref is "in and out" — the value must be set before the call and the method can read and change it.
Q: Why won't C# let me overload by return type?
When you write Multiply(2, 3), the compiler chooses the overload from the arguments, not from what you do with the result. Two methods that differ only in return type would be ambiguous, so C# requires the parameter lists to differ.
Mini-Challenge: Write a Max Method
No blanks this time — just a brief and an outline. Write a method static int Max(int a, int b) that returns the larger of its two arguments, then call it on a few pairs in Main and print the results. Run it and check your output against the expected lines in the comments.
using System;
class Program
{
// 🎯 MINI-CHALLENGE: write a Max method
// 1. Write a method static int Max(int a, int b)
// that RETURNS the larger of the two numbers.
// (Hint: an if statement, or the ternary a > b ? a : b)
// 2. In Main, call it on a few pairs and print the results.
//
// ✅ Expected output:
// Max(3, 9) = 9
// Max(12, 4) = 12
// Max(7, 7) = 7
// write your Max method here
static void Main()
{
// call Max(...) and print the three lines above
}
}🎉 Lesson Complete
- ✅ A method bundles code under a name with parameters (inputs) you call using arguments
- ✅ void methods perform actions; typed methods hand a value back with return
- ✅ Expression-bodied syntax (=>) is shorthand for a one-line return
- ✅ Parameters can be optional (default), named, or params for any count
- ✅ out hands back extra values; ref reads and updates the caller's variable
- ✅ Overloading reuses a name across different parameter signatures; scope keeps locals private to their method
Practice quiz
What return type does a method use when it does something but returns no value?
- null
- empty
- void
- int
Answer: void. void means the method performs an action and hands nothing back.
What is the difference between a parameter and an argument?
- A parameter is the placeholder in the definition; an argument is the value passed at the call
- They are the same thing
- A parameter is the value passed; an argument is the placeholder
- Arguments only exist for void methods
Answer: A parameter is the placeholder in the definition; an argument is the value passed at the call. Definition side = parameter; call side = argument.
What does the expression-bodied syntax => expr replace?
- A loop
- A constructor
- An if statement
- A { return expr; } body
Answer: A { return expr; } body. => is shorthand for a one-line method body that returns the expression.
Where must optional (default-valued) parameters appear in the parameter list?
- First
- After all required parameters
- Anywhere
- Only alone
Answer: After all required parameters. Optional parameters must come after all required ones, or the code won't compile (CS1737).
What does the params keyword allow?
- Accepting any number of arguments as an array
- Returning multiple values
- Naming arguments
- Default values
Answer: Accepting any number of arguments as an array. params lets a method accept a variable number of arguments collected into an array.
Which keyword lets a method hand back an extra value, as int.TryParse does?
- ref
- params
- out
- static
Answer: out. out lets a method produce an extra output; it must be assigned before the method returns.
What makes two methods valid overloads of the same name?
- Different return types only
- Different parameter lists (type or count)
- Different names
- Being in different files
Answer: Different parameter lists (type or count). Overloads must differ in their parameters; the return type alone cannot distinguish them.
What must a recursive method have to avoid running forever?
- A loop
- An out parameter
- A static field
- A base case that stops the recursion
Answer: A base case that stops the recursion. Recursion needs a base case (e.g. n <= 1) that returns without recursing further.
By default, passing an int to a method passes it how?
- By reference
- By value (a copy)
- As an out parameter
- It cannot be passed
Answer: By value (a copy). Value types are passed by value — the method gets a copy, so changes don't affect the caller's variable.
What does a named argument like F(times: 2, msg: "Hi") allow?
- Skipping the method body
- Returning two values
- Passing arguments by name in any order
- Making the method static
Answer: Passing arguments by name in any order. Named arguments let you pass values by parameter name in any order, making calls self-documenting.
Continue this course
- Previous: Loops
- Next: Object-Oriented Programming — Create classes with properties, constructors, and encapsulation
- Quick reference: C# cheat sheet