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

💡 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

FeatureSyntaxMeaning
Void methodstatic void Name()Does something, returns nothing
Return valuestatic int Name()Must return an int
Expression bodystatic int F() => expr;One-liner shorthand for return
Optional paramvoid F(int x = 5)Uses 5 if the caller omits it
Params arrayvoid F(params int[] n)Any number of arguments
Out parameterbool F(out int r)Hands back an extra value
Named argsF(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 A

Your 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.91

Notice 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

Common Errors (and the fix)

📋 Quick Reference

TaskCodeNotes
Action, no resultstatic void Log(string m)No return value
Return a valuestatic int Add(int a, int b)return a + b;
One-linerstatic int Sq(int n) => n * n;Expression body
Default valuevoid F(int x = 0)Optional param (last)
Call by nameF(x: 5)Named argument
Many argumentsint Sum(params int[] n)Sum(1, 2, 3)
Extra outputbool TryX(out int r)Assign r before return
OverloadF(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

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