Lesson 17 · Functions

Creating Functions

Master user-defined Functions in VB 2026 — the critical difference from Sub procedures, typed return values, multiple Return paths, overloading, recursion, lambda expressions, and when to use a Function vs a Sub.

Key Takeaway: A Function is like a Sub with one crucial addition — it returns a value using Return value. The return type is declared after As at the end of the signature. Use a Function when the purpose is to compute and deliver a result (e.g. IsValidEmail, CalculateTotal, FormatCurrency). Use a Sub when the purpose is to perform an action with no meaningful result (e.g. ClearForm, ShowAlert). Functions make code self-documenting: the caller reads If IsValidEmail(txt) and immediately knows a Boolean is returned.

17.1 Function vs Sub — The One Key Difference

Sub Procedure (Lesson 16)
' Does NOT return a value
Private Sub ShowGreeting(name As String)
    lblMsg.Text = "Hello, " & name
End Sub

' Called as a statement (no result)
ShowGreeting("Alice")
Function (This Lesson)
' RETURNS a value (String here)
Private Function BuildGreeting(name As String) As String
    Return "Hello, " & name
End Function

' Called in an expression
lblMsg.Text = BuildGreeting("Alice")
AspectSubFunction
Returns a valueNoYes — declared with As Type
KeywordSub / End SubFunction / End Function
How to use resultN/A (can use ByRef params)In expressions, assignments, conditions
Exit keywordReturnReturn value
Use when…Performing an actionComputing & delivering a result

17.2 Basic Function Syntax

BasicFunctions.vb — Visual Basic 2026
' Simplest possible function
Private Function Square(n As Double) As Double
    Return n * n
End Function

lblResult.Text = Square(7).ToString()           ' "49"
lblResult.Text = Square(Square(2)).ToString()    ' "16" -- can nest calls

' Function with input validation and multiple Return paths
Private Function Divide(a As Double, b As Double) As String
    If b = 0 Then Return "Cannot divide by zero"
    Return (a / b).ToString("G")
End Function

lblDiv.Text = Divide(10, 3)    ' "3.33333333333333"
lblDiv.Text = Divide(5,  0)    ' "Cannot divide by zero"

' Boolean predicate -- name starts with "Is" or "Has" by convention
Private Function IsEven(n As Integer) As Boolean
    Return n Mod 2 = 0
End Function

If IsEven(42) Then lblStatus.Text = "Even!"

' Function called in a loop -- building a formatted list
Private Function FormatScore(name As String, score As Integer) As String
    Dim grade = If(score >= 90, "A+", If(score >= 80, "A", If(score >= 70, "B", If(score >= 60, "C", "F"))))
    Return $"{name,-20} {score,3}  {grade}"
End Function

lstScores.Items.Add(FormatScore("Alice Lim",   92))
lstScores.Items.Add(FormatScore("Bob Tan",     74))
lstScores.Items.Add(FormatScore("Carol Wong",  58))
Try It — Simulation 17.1: Function Tester

Choose a function, enter arguments, and see the return value. Each function shows its signature, the computation inside, and the exact value returned.

Function Tester
Function:
Argument(s):

17.3 Return Types

A Function must declare its return type after As. Any VB data type — including custom classes and arrays — can be a return type. Functions that could fail should return a Nullable type (Integer?) or a special sentinel value, not throw exceptions for expected failures.

ReturnTypes.vb — Visual Basic 2026
' Integer return
Private Function Factorial(n As Integer) As Long
    If n <= 1 Then Return 1
    Dim result As Long = 1
    For i As Integer = 2 To n
        result *= i
    Next
    Return result
End Function

lblFact.Text = Factorial(10).ToString()   ' "3628800"

' Double return -- interest calculation
Private Function CompoundInterest(principal As Decimal, rate As Double,
                                   years As Integer) As Decimal
    Return CDec(principal * (1 + rate) ^ years)
End Function

lblFV.Text = CompoundInterest(10000, 0.05, 10).ToString("C2")   ' "$16,288.95"

' Boolean return -- email validator
Private Function IsValidEmail(email As String) As Boolean
    If String.IsNullOrWhiteSpace(email) Then Return False
    Dim atPos = email.IndexOf("@"c)
    If atPos < 1 Then Return False
    Dim dotPos = email.LastIndexOf("."c)
    Return dotPos > atPos + 1 AndAlso dotPos < email.Length - 1
End Function

btnSubmit.Enabled = IsValidEmail(txtEmail.Text)

' String return -- full name formatter
Private Function FormatFullName(first As String, last As String,
                                   Optional title As String = "") As String
    Dim base = (first.Trim() & " " & last.Trim()).Trim()
    Return If(title <> "", title & " " & base, base)
End Function

lblName.Text = FormatFullName("Alice", "Lim")            ' "Alice Lim"
lblName.Text = FormatFullName("Alice", "Lim", "Dr.")    ' "Dr. Alice Lim"

' Nullable return -- safe TryParse wrapper
Private Function TryParseInt(s As String) As Integer?
    Dim n As Integer
    Return If(Integer.TryParse(s, n), n, CType(Nothing, Integer?))
End Function

Dim age = TryParseInt(txtAge.Text)
If age.HasValue Then
    lblAge.Text = $"Age: {age.Value}"
Else
    lblAge.Text = "Not a number"
End If

17.4 Recursive Functions

A recursive function calls itself. Every recursive function needs two things: a base case that stops recursion, and a recursive case that moves toward the base case. Infinite recursion causes a StackOverflowException.

Recursion.vb — Visual Basic 2026
' Factorial (recursive) -- base case: n = 0 or 1
Private Function FactR(n As Integer) As Long
    If n <= 1 Then Return 1        ' base case
    Return n * FactR(n - 1)          ' recursive case
End Function

' FactR(5) = 5 * FactR(4)
'           = 5 * 4 * FactR(3)
'           = 5 * 4 * 3 * FactR(2)
'           = 5 * 4 * 3 * 2 * FactR(1)
'           = 5 * 4 * 3 * 2 * 1 = 120

' Fibonacci (recursive)
Private Function FibR(n As Integer) As Long
    If n <= 1 Then Return n           ' base cases: Fib(0)=0, Fib(1)=1
    Return FibR(n - 1) + FibR(n - 2)  ' two recursive calls
End Function

' GCD using Euclid's algorithm (recursive)
Private Function GCD(a As Integer, b As Integer) As Integer
    If b = 0 Then Return a            ' base case
    Return GCD(b, a Mod b)            ' recursive: GCD(48,18) -> GCD(18,12) -> GCD(12,6) -> GCD(6,0)
End Function

lblGCD.Text = GCD(48, 18).ToString()   ' "6"

' Power function (recursive): base ^ exponent
Private Function Pow(base_ As Double, exp As Integer) As Double
    If exp = 0 Then Return 1                                ' base case: x^0 = 1
    If exp < 0 Then Return 1 / Pow(base_, -exp)             ' handle negatives
    Return base_ * Pow(base_, exp - 1)
End Function
Recursion Depth Warning

VB 2026 has a finite call stack. Deep recursion on large inputs (e.g. FactR(10000)) will crash with StackOverflowException. For production code, prefer the iterative version for factorial and Fibonacci. Use recursion where it models the problem naturally (tree traversal, file system, JSON parsing) and where the recursion depth is bounded and small.

Try It — Simulation 17.2: Recursion Step Explorer

Watch a recursive function unwind step by step. See the call stack grow as recursive calls are made, then collapse as return values propagate back up.

Recursion Step Explorer
Function:
n:

17.5 Function Overloading

VB 2026 allows you to define multiple Functions with the same name as long as their parameter lists differ (different number or types of parameters). The compiler picks the right version based on the arguments supplied at the call site.

Overloading.vb — Visual Basic 2026
' Overloaded Add -- different parameter types
Private Function Add(a As Integer, b As Integer) As Integer
    Return a + b
End Function

Private Function Add(a As Double, b As Double) As Double
    Return a + b
End Function

Private Function Add(a As String, b As String) As String
    Return a.Trim() & " " & b.Trim()
End Function

Add(2, 3)             ' 5           (Integer version called)
Add(2.5, 3.7)        ' 6.2         (Double version called)
Add("Alice", "Lim")  ' "Alice Lim" (String version called)

' Practical overload: FormatMoney
Private Function FormatMoney(amount As Decimal) As String
    Return amount.ToString("C2")        ' uses current culture
End Function

Private Function FormatMoney(amount As Decimal, currency As String) As String
    Return $"{currency} {amount:N2}"
End Function

Private Function FormatMoney(amount As Decimal, currency As String, showSymbol As Boolean) As String
    Return If(showSymbol, $"{currency} {amount:N2}", amount.ToString("N2"))
End Function

FormatMoney(1234.5)                        ' "$1,234.50"
FormatMoney(1234.5, "RM")                  ' "RM 1,234.50"
FormatMoney(1234.5, "USD", False)          ' "1,234.50"
Overloading vs Optional Parameters

Overloading and Optional parameters often solve the same problem. Prefer Optional when the logic is identical and you just want to omit some parameters. Prefer overloading when the logic itself differs between versions, or when working with different parameter types. Overloading gives IntelliSense separate documentation entries for each version, which can be helpful for complex APIs.


17.6 Lambda Expressions — Inline Functions

A lambda expression is a short, anonymous function defined inline. VB 2026 supports single-line lambda functions (return a value) and single-line lambda subs (no return value). Lambdas are most useful with LINQ, events, and callbacks.

Lambda.vb — Visual Basic 2026
' Single-line lambda function: Function(params) expression
Dim square    = Function(x As Double) x * x
Dim greet     = Function(name As String) "Hello, " & name
Dim isAdult   = Function(age As Integer) age >= 18

lblResult.Text = square(5).ToString()       ' "25"
lblResult.Text = greet("Bob")               ' "Hello, Bob"
If isAdult(20) Then lblStatus.Text = "Adult"

' Pass lambda to a method -- Func(Of T, TResult) delegate
Dim numbers = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8}

' LINQ with lambda: filter and transform
Dim evens  = numbers.Where(Function(n) n Mod 2 = 0)    ' {2,4,6,8}
Dim squares = numbers.Select(Function(n) n * n)         ' {1,4,9,16,25,36,49,64}
Dim bigEvens = numbers.Where(Function(n) n Mod 2 = 0 AndAlso n > 4) ' {6,8}

lstResult.DataSource = evens.ToList()

' Multi-line lambda function
Dim classify = Function(score As Integer) As String
                    If score >= 90 Then Return "A+"
                    If score >= 80 Then Return "A"
                    If score >= 70 Then Return "B"
                    Return "F"
                End Function

lblGrade.Text = classify(85)   ' "A"

' Lambda Sub (no return value)
Dim logItem = Sub(msg As String) lstLog.Items.Add($"[{DateTime.Now:HH:mm:ss}] {msg}")
logItem("App started")
logItem("Connection established")

17.7 Practical Examples

Example 17.1 — Financial Functions

FinanceFunctions.vb — Visual Basic 2026
' Monthly mortgage payment: P * r*(1+r)^n / ((1+r)^n - 1)
Private Function MortgagePayment(principal As Decimal,
                                  annualRate As Double,
                                  years As Integer) As Decimal
    If annualRate = 0 Then Return principal / (years * 12)   ' zero-interest edge case
    Dim r = annualRate / 12
    Dim n = years * 12
    Return CDec(CDbl(principal) * r * (1 + r) ^ n / ((1 + r) ^ n - 1))
End Function

' Compound interest: A = P*(1 + r/n)^(n*t)
Private Function FutureValue(principal As Decimal, annualRate As Double,
                               compoundsPerYear As Integer, years As Integer) As Decimal
    Return CDec(CDbl(principal) * (1 + annualRate / compoundsPerYear) ^ (compoundsPerYear * years))
End Function

' Simple VAT calculator
Private Function AddSST(amount As Decimal, Optional rate As Double = 0.06) As Decimal
    Return Math.Round(amount * CDec(1 + rate), 2)
End Function

' Usage in event handler
Private Sub btnCalculate_Click(sender As Object, e As EventArgs) Handles btnCalculate.Click
    Dim p As Decimal
    Dim r As Double
    Dim y As Integer
    If Not Decimal.TryParse(txtPrincipal.Text, p) Then Return
    If Not Double.TryParse(txtRate.Text, r)     Then Return
    If Not Integer.TryParse(txtYears.Text, y)   Then Return

    lblMonthly.Text = MortgagePayment(p, r / 100, y).ToString("C2")
    lblFV.Text      = FutureValue(p, r / 100, 12, y).ToString("C2")
End Sub
Try It — Simulation 17.3: Finance Function Calculator

Four financial functions working together: mortgage, compound interest, SST, and loan affordability check. Each shows its return value and the formula used.

Finance Function Calculator
Principal (RM):
Annual Rate (%):
Years:
Monthly Income (RM):
SST Rate (%):
Try It — Simulation 17.4: Lambda & LINQ Explorer

Enter comma-separated numbers and choose a lambda operation. See the lambda expression, input, and transformed output side by side.

Lambda & LINQ Explorer
Numbers (comma-separated):
Try It — Simulation 17.5: Function Library

A library of user-defined functions — string formatters, validators, converters and math utilities. Each shows signature, input, and exact return value.

User-Defined Function Library
Category:
Function:
Argument:

17.8 GitHub Copilot — Function Patterns

GitHub Copilot — Input Validator Functions
You typed: ' Create Boolean functions: IsValidEmail, IsValidPhone, IsStrongPassword(min 8 chars, 1 upper, 1 digit, 1 special). Return True/False only -- no UI side effects.'
' Boolean validator functions -- pure, no side effects.
Private Function IsValidEmail(email As String) As Boolean
    If String.IsNullOrWhiteSpace(email) Then Return False
    Dim atIdx = email.IndexOf("@"c)
    If atIdx < 1 Then Return False
    Dim dotIdx = email.LastIndexOf("."c)
    Return dotIdx > atIdx + 1 AndAlso dotIdx < email.Length - 1
End Function

Private Function IsValidPhone(phone As String) As Boolean
    Dim digits = phone.Where(Function(c) Char.IsDigit(c)).Count()
    Return digits >= 8 AndAlso digits <= 15
End Function

Private Function IsStrongPassword(pw As String) As Boolean
    If pw.Length < 8 Then Return False
    Dim hasUpper   = pw.Any(Function(c) Char.IsUpper(c))
    Dim hasDigit   = pw.Any(Function(c) Char.IsDigit(c))
    Dim hasSpecial = pw.Any(Function(c) "!@#$%^&*".Contains(c))
    Return hasUpper AndAlso hasDigit AndAlso hasSpecial
End Function

' Compose validators in UI:
btnRegister.Enabled = IsValidEmail(txtEmail.Text) AndAlso
                      IsValidPhone(txtPhone.Text) AndAlso
                      IsStrongPassword(txtPass.Text)
GitHub Copilot — Recursive Directory Size
You typed: ' Write a recursive Function GetFolderSize(path As String) As Long that returns total bytes of all files recursively. Handle UnauthorizedAccessException silently.'
' Recursive folder size calculator -- handles access errors silently.
Private Function GetFolderSize(path As String) As Long
    Dim total As Long = 0
    Try
        ' Sum sizes of files in this folder
        For Each file As String In Directory.GetFiles(path)
            Try
                total += New FileInfo(file).Length
            Catch ex As UnauthorizedAccessException
            End Try
        Next
        ' Recurse into sub-folders
        For Each subDir As String In Directory.GetDirectories(path)
            total += GetFolderSize(subDir)   ' recursive call
        Next
    Catch ex As UnauthorizedAccessException
    End Try
    Return total
End Function

Private Function FormatBytes(bytes As Long) As String
    If bytes >= 1073741824 Then Return $"{bytes / 1073741824.0:F2} GB"
    If bytes >= 1048576    Then Return $"{bytes / 1048576.0:F2} MB"
    If bytes >= 1024       Then Return $"{bytes / 1024.0:F2} KB"
    Return $"{bytes} bytes"
End Function

lblSize.Text = FormatBytes(GetFolderSize(txtPath.Text))
Copilot Chat Prompts for This Lesson

Try these in the Copilot Chat panel while writing Functions:

  • "Write a Function ClampValue(value, min, max As Double) As Double that returns value constrained to the [min, max] range"
  • "Create an overloaded Function FormatDate that accepts a Date or a String and returns 'dd MMM yyyy' format"
  • "Write a recursive Function CountDigits(n As Integer) As Integer using recursion, with base case n < 10"
  • "Use LINQ with lambda to take a List(Of Student) and return only students where Score >= 50, sorted by Score descending"

Lesson Summary

  • A Function is identical to a Sub except it declares a return type after As and uses Return value to deliver the result. Functions are called inside expressions: lblResult.Text = FormatScore(name, 85).
  • Use a Function when the purpose is to compute and deliver a value. Use a Sub when the purpose is to perform an action. The name should reflect this: IsValidEmail (Function, returns Boolean), ClearForm (Sub, performs action).
  • A Function can have multiple Return paths (early returns for guard clauses). The compiler ensures every code path returns a compatible value. Use Nullable types (Integer?) as the return type when the function might have no result.
  • Recursive Functions call themselves. Every recursive function requires a base case to stop recursion and a recursive case that progresses toward it. Avoid deep recursion on unbounded inputs — use an iterative alternative for safety.
  • Function Overloading: multiple Functions with the same name but different parameter signatures. The compiler selects the right version at compile time. Prefer overloading when different parameter types warrant different logic; prefer Optional parameters when the logic is the same.
  • Lambda expressions are inline, anonymous functions: Function(x As Integer) x * x. Used extensively with LINQ (.Where, .Select, .OrderBy) and delegates. Multi-line lambdas use Function(...) As T ... End Function syntax.

Exercises

Exercise 17.1 — String Utility Library

  • Write Function TitleCase(s As String) As String — capitalise first letter of each word
  • Write Function CountWords(s As String) As Integer — split on spaces and count non-empty parts
  • Write Function IsPalindrome(s As String) As Boolean — compare reversed string (case-insensitive)
  • Write Function Truncate(s As String, max As Integer, Optional ellipsis As Boolean = True) As String
  • Display results for several test strings in a ListBox
  • Copilot challenge: Ask Copilot to "add a Function WordFrequency(text As String) As Dictionary(Of String, Integer) counting each word"

Exercise 17.2 — Recursive Tower of Hanoi

  • Write Function HanoiMoves(n As Integer) As Long that returns 2^n - 1 (the minimum moves needed)
  • Write Sub SolveHanoi(n, from, to_, via As String) (recursive) that adds each move to a ListBox
  • Call for n = 1 to 5 and display move counts and the full solution for n = 3
  • Copilot challenge: Ask Copilot to "animate the Hanoi solution using a Timer with step delays"

Exercise 17.3 — Lambda LINQ Grade Report

  • Create a List(Of Student) where Student has Name (String) and Score (Integer)
  • Use LINQ lambdas to: find the top 3 students, filter fails (<50), compute the class average
  • Use .Where, .OrderByDescending, .Take, .Average, .Select with lambdas
  • Display results in labelled ListBoxes
  • Copilot challenge: Ask Copilot to "add a Function GetLetterGrade(score) As String and use .Select to project each student to a formatted 'Name: Grade' string"

Next: Lesson 18 — Math Functions

Explore VB 2026's built-in Math library — Math.Sqrt, Math.Pow, Math.Abs, Math.Round, Math.Floor, Math.Ceiling, and more with practical examples.

Continue »

Related Resources


Featured Books

Visual Basic 2022 Made Easy

Visual Basic 2022 Made Easy

by Dr. Liew Voon Kiong

User-defined functions with real applications including validators, formatters, and financial calculators.

View on Amazon →
Visual Basic 2026 Made Easy

Visual Basic 2026 Made Easy

by Dr. Liew Voon Kiong

48 complete programs making heavy use of reusable user-defined functions for data processing and UI.

View on Amazon →