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.
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
' 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")
' 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")
| Aspect | Sub | Function |
|---|---|---|
| Returns a value | No | Yes — declared with As Type |
| Keyword | Sub / End Sub | Function / End Function |
| How to use result | N/A (can use ByRef params) | In expressions, assignments, conditions |
| Exit keyword | Return | Return value |
| Use when… | Performing an action | Computing & delivering a result |
17.2 Basic Function Syntax
' 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))
Choose a function, enter arguments, and see the return value. Each function shows its signature, the computation inside, and the exact value returned.
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.
' 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.
' 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
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.
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.
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.
' 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 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.
' 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
' 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
Four financial functions working together: mortgage, compound interest, SST, and loan affordability check. Each shows its return value and the formula used.
Enter comma-separated numbers and choose a lambda operation. See the lambda expression, input, and transformed output side by side.
A library of user-defined functions — string formatters, validators, converters and math utilities. Each shows signature, input, and exact return value.
17.8 GitHub Copilot — Function Patterns
' 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)
' 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))
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
Asand usesReturn valueto 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 useFunction(...) As T ... End Functionsyntax.
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 Longthat 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)whereStudenthas 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,.Selectwith 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"
Related Resources
← Lesson 16
Sub Procedures — ByVal, ByRef, Optional, ParamArray.
Lesson 18 →
Math Functions — built-in Math library.
MS Docs — Function Procedures
Complete VB.NET reference for Functions, return types and overloading.
MS Docs — Lambda Expressions
Single-line and multi-line lambdas in VB.NET with examples.
Featured Books
Visual Basic 2022 Made Easy
User-defined functions with real applications including validators, formatters, and financial calculators.
View on Amazon →
Visual Basic 2026 Made Easy
48 complete programs making heavy use of reusable user-defined functions for data processing and UI.
View on Amazon →