Lesson 16 · Sub Procedures

Sub Procedures

Organise your VB 2026 code into reusable, named Sub procedures — no-parameter Subs, ByVal vs ByRef parameters, Optional parameters, ParamArray, procedure scope and access modifiers, the call stack, and how Event handlers are themselves special Sub procedures.

Key Takeaway: A Sub procedure performs a task but does not return a value (that is a Function's job, covered in Lesson 17). Use ByVal (the default) when you want to pass a copy of the variable — the caller's variable is protected. Use ByRef when the Sub must modify the caller's variable. Keep Subs small and focused on one responsibility. Name them with a clear verb phrase: ValidateForm, ClearAllFields, DisplayResults.

16.1 What is a Sub Procedure?

A Sub procedure is a named, reusable block of code that performs a specific task. When the Sub finishes, control returns to the line after the call. Unlike a Function (Lesson 17), a Sub does not return a value.

You have already been writing Sub procedures in every lesson — every event handler (btnClick_Click, txtName_TextChanged) is a special Sub procedure that VB calls automatically when the user interacts with the UI.

Without Sub Procedures (repetitive)
' Button 1 Click
txtName.Clear()
txtEmail.Clear()
txtPhone.Clear()
lblStatus.Text = ""

' Button 2 Click (same code again!)
txtName.Clear()
txtEmail.Clear()
txtPhone.Clear()
lblStatus.Text = ""
With a Sub Procedure (DRY)
Private Sub ClearForm()
    txtName.Clear()
    txtEmail.Clear()
    txtPhone.Clear()
    lblStatus.Text = ""
End Sub

' Button 1 Click
ClearForm()

' Button 2 Click
ClearForm()
DRY Principle

Don't Repeat Yourself: if you find yourself copying and pasting the same block of code in two or more places, extract it into a Sub procedure. Changes only need to be made in one place. This is the single most important refactoring habit you can build as a programmer.


16.2 Defining and Calling a Sub

BasicSub.vb — Visual Basic 2026
' Define a Sub with no parameters
Private Sub DisplayWelcome()
    lblWelcome.Text = "Welcome to VB 2026!"
    lblWelcome.ForeColor = Color.DarkBlue
    lblWelcome.Font = New Font("Segoe UI", 14, FontStyle.Bold)
End Sub

' Call the Sub -- use the name followed by parentheses
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    DisplayWelcome()        ' called when form opens
End Sub

Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
    DisplayWelcome()        ' called again from button
End Sub

' A Sub can call other Subs -- building a pipeline
Private Sub ProcessOrder()
    ValidateOrder()
    CalculateTotals()
    UpdateInventory()
    SendConfirmation()
End Sub

' Early exit with Return (like Break for loops)
Private Sub SaveRecord()
    If String.IsNullOrWhiteSpace(txtName.Text) Then
        lblError.Text = "Name is required."
        Return               ' exit the Sub early
    End If
    ' ... rest of save logic only runs if name is filled in
End Sub

16.3 Parameters — Passing Data Into a Sub

Parameters allow you to pass data into a Sub so it can work with different values each time it is called, making the Sub truly reusable. Declare parameters inside the parentheses: name As Type.

Parameters.vb — Visual Basic 2026
' Sub with one parameter
Private Sub ShowMessage(message As String)
    lblStatus.Text = message
End Sub

ShowMessage("File saved successfully.")
ShowMessage("Error: connection failed.")

' Sub with multiple parameters
Private Sub DisplayStudentInfo(name As String, score As Integer, grade As String)
    lstResults.Items.Add($"{name,-20} {score,3}   {grade}")
End Sub

DisplayStudentInfo("Alice Lim",    92, "A")
DisplayStudentInfo("Bob Tan",      74, "B")
DisplayStudentInfo("Carol Wong",   85, "A")

' Parameters vs Arguments
' PARAMETER = variable declared in the Sub definition
' ARGUMENT  = actual value passed when calling the Sub
Private Sub SetLabelColour(lbl As Label, colour As Color)
    lbl.BackColor = colour
End Sub

SetLabelColour(lblName,  Color.LightYellow)     ' lbl=lblName, colour=LightYellow
SetLabelColour(lblEmail, Color.LightPink)        ' lbl=lblEmail, colour=LightPink

' Named arguments (VB 2026 supports named arguments for clarity)
DisplayStudentInfo(name:="Diana", score:=88, grade:="A")   ' named args

16.4 ByVal vs ByRef — The Critical Difference

This is one of the most important concepts in VB 2026. When you pass a variable to a Sub, how it is passed determines whether the Sub can change the caller's original variable.

KeywordWhat is passedCaller's variableWhen to use
ByVal (default)A copy of the valueUnchanged (protected)Reading data; most parameters should be ByVal
ByRefA reference to the variableModified by the SubWhen the Sub must update the caller's variable
ByValByRef.vb — Visual Basic 2026
' ByVal -- caller's variable is NOT changed
Private Sub TryDouble(ByVal n As Integer)
    n = n * 2             ' only changes the local copy
End Sub

Dim x = 5
TryDouble(x)
' x is still 5 -- ByVal protected it
lblResult.Text = x.ToString()   ' "5"

' ByRef -- caller's variable IS changed
Private Sub ActuallyDouble(ByRef n As Integer)
    n = n * 2             ' modifies the original variable
End Sub

Dim y = 5
ActuallyDouble(y)
lblResult.Text = y.ToString()   ' "10" -- y was modified

' Practical ByRef: swap two values
Private Sub Swap(ByRef a As Integer, ByRef b As Integer)
    Dim temp = a
    a = b
    b = temp
End Sub

Dim p = 10, q = 20
Swap(p, q)
lblResult.Text = $"p={p}  q={q}"   ' "p=20  q=10"

' Practical ByRef: multiple "outputs" when you can't use a Function
Private Sub ParseName(fullName As String, ByRef firstName As String, ByRef lastName As String)
    Dim parts = fullName.Split(" "c)
    firstName = If(parts.Length > 0, parts(0), "")
    lastName  = If(parts.Length > 1, parts(1), "")
End Sub

Dim fn = "", ln = ""
ParseName("Alice Lim", fn, ln)
lblFirst.Text = fn    ' "Alice"
lblLast.Text  = ln    ' "Lim"
Try It — Simulation 16.1: ByVal vs ByRef Demonstrator

Enter a starting value, choose ByVal or ByRef, and run the Sub. Watch whether the caller's variable changes — and see the call stack frame appear and disappear.

ByVal vs ByRef Demo
Starting value of x:
Passing mode:
Operation:
Call Stack:

16.5 Optional Parameters

Optional parameters have a default value. Callers may omit them; if omitted the default is used. All optional parameters must come after all required parameters.

Optional.vb — Visual Basic 2026
' Optional parameter with a default value
Private Sub ShowAlert(message As String, Optional title As String = "Alert", Optional beep As Boolean = False)
    If beep Then Console.Beep()
    MsgBox(message, MsgBoxStyle.Information, title)
End Sub

' All three ways to call it:
ShowAlert("File saved.")                            ' title="Alert", beep=False
ShowAlert("Saved.", "Success")                    ' beep=False
ShowAlert("Low disk space!", "Warning", True)      ' all three provided

' More practical: formatted display sub
Private Sub AddListItem(lst As ListBox, text As String,
                         Optional prefix As String = "",
                         Optional upperCase As Boolean = False)
    Dim item = If(prefix <> "", prefix & " ", "") & text
    lst.Items.Add(If(upperCase, item.ToUpper(), item))
End Sub

AddListItem(lstLog, "Server started")                          ' "Server started"
AddListItem(lstLog, "Connection failed", "[ERROR]")          ' "[ERROR] Connection failed"
AddListItem(lstLog, "disk full",           "[WARN]", True)   ' "[WARN] DISK FULL"

16.6 ParamArray — Variable Number of Arguments

ParamArray lets a Sub accept any number of arguments of the same type. The arguments are collected into an array inside the Sub. ParamArray must be the last parameter and cannot be combined with Optional.

ParamArray.vb — Visual Basic 2026
' ParamArray -- accepts any number of Double values
Private Sub ShowStats(ParamArray values() As Double)
    If values.Length = 0 Then
        lblStats.Text = "No values provided." : Return
    End If
    Dim total = values.Sum()
    Dim avg   = total / values.Length
    Dim min   = values.Min()
    Dim max   = values.Max()
    lblStats.Text = $"Count={values.Length}  Sum={total:F1}  Avg={avg:F2}  Min={min}  Max={max}"
End Sub

ShowStats(10, 20, 30)                      ' 3 values
ShowStats(5, 15, 25, 35, 45)               ' 5 values
ShowStats(100)                             ' 1 value
ShowStats()                                ' 0 values -- handled by If check

' Mixed: required param + ParamArray
Private Sub LogMessage(category As String, ParamArray parts() As String)
    Dim combined = String.Join(" | ", parts)
    lstLog.Items.Add($"[{category}] {combined}")
End Sub

LogMessage("INFO",  "App started")
LogMessage("ERROR", "File not found", "C:\data.txt", "Errno 2")
LogMessage("AUDIT", "Login", "admin", "192.168.1.1", "success")
Try It — Simulation 16.2: Parameter Playground

Switch between no-parameter, ByVal, ByRef, Optional, and ParamArray Sub forms. See exactly what each call produces and how the caller's variable is affected.

Parameter Playground
Sub type:
Input value:

16.7 Scope and Access Modifiers

Sub procedures have an access modifier that controls which code can call them.

ModifierVisible toTypical use
PrivateCurrent class/form onlyInternal helpers; event handlers (most Subs)
PublicAny code in the projectUtility Subs shared across forms
ProtectedCurrent class + subclassesBase class helpers in OOP (Lesson 25)
FriendSame assemblySubs shared within the project but not outside
Shared / StaticCalled on the class, not an instanceUtility methods that don't need form state
Scope.vb — Visual Basic 2026
' Private -- only Form1 can call this
Private Sub UpdateStatusBar(message As String)
    lblStatus.Text = message
    lblStatus.ForeColor = Color.DarkGreen
End Sub

' Public -- any form in the project can call this
Public Sub ClearAllControls(ParamArray controls() As Control)
    For Each ctrl As Control In controls
        If TypeOf ctrl Is TextBox Then CType(ctrl, TextBox).Clear()
        If TypeOf ctrl Is Label   Then CType(ctrl, Label).Text = ""
    Next
End Sub

' Shared -- called on class, not on an instance
Public Shared Sub LogError(source As String, message As String)
    System.IO.File.AppendAllText("errors.log",
        $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {source}: {message}{Environment.NewLine}")
End Sub

' Call the Shared sub without creating an instance:
FormHelper.LogError("SaveRecord", "Database timeout")

16.8 Event Handlers are Sub Procedures

Every event handler you have written since Lesson 1 is a Sub procedure with a special signature. The Handles clause tells VB which control and event this Sub should respond to. A single Sub can handle multiple events, and a single event can be handled by multiple Subs.

EventHandlers.vb — Visual Basic 2026
' Standard event handler signature
Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
    SaveRecord()            ' delegate to a helper Sub
End Sub

' One Sub handles multiple events (Handles clause with commas)
Private Sub TextBox_TextChanged(sender As Object, e As EventArgs) _
    Handles txtName.TextChanged, txtEmail.TextChanged, txtPhone.TextChanged
    ValidateForm()          ' validate whenever any text box changes
End Sub

' Shared validation logic -- called from multiple events
Private Sub ValidateForm()
    Dim nameOK  = Not String.IsNullOrWhiteSpace(txtName.Text)
    Dim emailOK = txtEmail.Text.Contains("@")
    Dim phoneOK = txtPhone.Text.Length >= 8
    btnSubmit.Enabled = nameOK AndAlso emailOK AndAlso phoneOK
    lblStatus.Text = If(btnSubmit.Enabled, "Ready to submit", "Please complete all fields")
End Sub

' sender parameter tells you WHICH control raised the event
Private Sub Button_Click(sender As Object, e As EventArgs) Handles btn1.Click, btn2.Click, btn3.Click
    Dim btn = CType(sender, Button)
    lblClicked.Text = $"You clicked: {btn.Text}"
End Sub

' Wire event handler at runtime (without Handles clause)
AddHandler btnDynamic.Click, AddressOf btnDynamic_Click
RemoveHandler btnDynamic.Click, AddressOf btnDynamic_Click   ' remove when no longer needed
Try It — Simulation 16.3: Call Stack Explorer

See exactly how the call stack builds when Subs call other Subs. Choose a scenario and watch frames push and pop as execution flows through the Sub chain.

Call Stack Explorer
Scenario:
Call Stack (bottom = first called):

16.9 Practical Examples

Example 16.1 — Form with Reusable Validation Sub

RegistrationForm.vb — Visual Basic 2026
Public Class RegistrationForm

    Private Sub HighlightField(txt As TextBox, lbl As Label, errorMsg As String)
        txt.BackColor = Color.LightPink
        lbl.Text      = errorMsg
        lbl.ForeColor = Color.Red
        lbl.Visible   = True
    End Sub

    Private Sub ClearFieldError(txt As TextBox, lbl As Label)
        txt.BackColor = Color.White
        lbl.Text      = ""
        lbl.Visible   = False
    End Sub

    Private Sub ValidateForm(ByRef isValid As Boolean)
        isValid = True

        If String.IsNullOrWhiteSpace(txtName.Text) Then
            HighlightField(txtName, lblNameErr, "Name is required.")
            isValid = False
        Else
            ClearFieldError(txtName, lblNameErr)
        End If

        If Not txtEmail.Text.Contains("@") OrElse Not txtEmail.Text.Contains(".") Then
            HighlightField(txtEmail, lblEmailErr, "Enter a valid email.")
            isValid = False
        Else
            ClearFieldError(txtEmail, lblEmailErr)
        End If
    End Sub

    Private Sub btnRegister_Click(sender As Object, e As EventArgs) Handles btnRegister.Click
        Dim valid As Boolean
        ValidateForm(valid)         ' ByRef: valid gets updated inside sub
        If Not valid Then Return
        MsgBox("Registration successful!")
    End Sub

End Class
Try It — Simulation 16.4: Registration Form with Sub Procedures

A complete form demonstrating how Subs work together: ValidateField (ByRef), HighlightError, ClearError, and ResetForm — each responsible for one thing.

Registration Form
Full Name:
Email:
Age:
Password:
Try It — Simulation 16.5: ParamArray Stats Calculator

Enter a comma-separated list of numbers. The Sub receives them as a ParamArray and computes count, sum, average, min, max, and range.

ShowStats(ParamArray values() As Double)
Enter numbers (comma-separated):

16.10 GitHub Copilot — Sub Procedure Patterns

GitHub Copilot — Input Validation Suite
You typed: ' Create reusable validation subs: ValidateRequired(txt, lbl), ValidateEmail(txt, lbl), ValidateRange(txt, lbl, min, max). Each highlights the field pink and shows an error Label. ValidateForm calls all three.'
' Reusable validation subs for a registration form.
Private Sub ValidateRequired(txt As TextBox, lbl As Label)
    If String.IsNullOrWhiteSpace(txt.Text) Then
        txt.BackColor = Color.LightPink
        lbl.Text = "This field is required."
    Else
        txt.BackColor = Color.White
        lbl.Text = ""
    End If
End Sub

Private Sub ValidateEmail(txt As TextBox, lbl As Label)
    Dim valid = txt.Text.Contains("@") AndAlso txt.Text.Contains(".") _
                AndAlso txt.Text.Length >= 5
    txt.BackColor = If(valid, Color.White, Color.LightPink)
    lbl.Text      = If(valid, "", "Enter a valid email address.")
End Sub

Private Sub ValidateRange(txt As TextBox, lbl As Label, min As Integer, max As Integer)
    Dim n As Integer
    Dim valid = Integer.TryParse(txt.Text, n) AndAlso n >= min AndAlso n <= max
    txt.BackColor = If(valid, Color.White, Color.LightPink)
    lbl.Text      = If(valid, "", $"Enter a number between {min} and {max}.")
End Sub

Private Sub ValidateForm(ByRef isValid As Boolean)
    ValidateRequired(txtName,  lblNameErr)
    ValidateEmail   (txtEmail, lblEmailErr)
    ValidateRange   (txtAge,   lblAgeErr, 18, 99)
    isValid = lblNameErr.Text = "" AndAlso
              lblEmailErr.Text = "" AndAlso
              lblAgeErr.Text = ""
End Sub
GitHub Copilot — Audit Logger with ParamArray
You typed: ' Write a WriteAuditLog sub with category As String and ParamArray fields() As String. Format as CSV with timestamp and append to audit.csv. Handle IO exceptions silently.'
' Audit logger: WriteAuditLog(category, ParamArray fields)
Private Sub WriteAuditLog(category As String, ParamArray fields() As String)
    Try
        Dim timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
        Dim safeCat   = category.Replace(","c, ";"c)
        Dim safeFields = fields.Select(Function(f) f.Replace(","c, ";"c)).ToArray()
        Dim line = $"{timestamp},{safeCat},{String.Join(",", safeFields)}"
        System.IO.File.AppendAllText("audit.csv", line & Environment.NewLine)
    Catch ex As Exception
        ' Log to debug output but don't crash the app
        Debug.WriteLine($"WriteAuditLog failed: {ex.Message}")
    End Try
End Sub

' Usage:
WriteAuditLog("LOGIN",  "admin", "192.168.1.1", "success")
WriteAuditLog("SAVE",   "record 42", "updated")
WriteAuditLog("ERROR",  "NullRef", "SaveRecord", "line 87")
Copilot Chat Prompts for This Lesson

Try these in the Copilot Chat panel while writing Sub procedures:

  • "Refactor this event handler into smaller Sub procedures, one for validation, one for saving, one for updating the UI"
  • "Create a Sub that accepts ByRef min and max As Integer and scans an array to find both in a single pass"
  • "Write a Sub called SetControlState(enabled As Boolean, ParamArray controls() As Control) that enables or disables all passed controls at once"
  • "Convert all TextBox_TextChanged event handlers on this form to call a shared ValidateForm() Sub with ByRef isValid"

Lesson Summary

  • A Sub procedure performs a task without returning a value. Every event handler is a Sub. Define with Private Sub Name(params) ... End Sub; call with Name(args).
  • ByVal (default) passes a copy — the caller's variable is unchanged. ByRef passes a reference — the Sub can modify the caller's variable. Prefer ByVal unless modification is explicitly needed.
  • Optional parameters have a default value and may be omitted by the caller. They must follow all required parameters. Use named arguments (param:=value) for clarity when skipping optional params in the middle.
  • ParamArray accepts any number of arguments of the same type, collected into an array. It must be the last parameter. Use it for utility Subs like loggers, formatters, and stats calculators.
  • Access modifiers control visibility: Private for form-internal helpers (most Subs), Public for shared utilities. The Shared modifier lets a Sub be called on the class without an instance.
  • Event handlers use Handles to wire automatically. One Sub can handle multiple events (comma list in Handles). Use sender to identify which control raised the event. Use AddHandler / RemoveHandler for runtime wiring.
  • Keep each Sub small and focused — one responsibility. If a Sub does more than one thing, split it. Extract repeated code blocks into named Subs immediately (DRY principle).

Exercises

Exercise 16.1 — Form Helper Suite

  • Create a form with Name, Email, Age, and Phone TextBoxes
  • Write a Private Sub HighlightError(txt As TextBox, lbl As Label, msg As String) and ClearError counterpart
  • Write ValidateForm(ByRef isValid As Boolean) that calls the helpers for each field
  • Wire all TextChanged events to a single handler that calls ValidateForm and enables/disables the Submit button
  • Copilot challenge: Ask Copilot to "add a ResetForm Sub that clears all TextBoxes and error labels at once using For Each"

Exercise 16.2 — Swap & Sort

  • Write Sub Swap(ByRef a As Integer, ByRef b As Integer)
  • Write Sub BubbleSort(ByRef arr() As Integer) that uses your Swap Sub internally
  • Enter 5 numbers in TextBoxes, call BubbleSort, display the sorted result in a ListBox showing each pass
  • Verify that after calling BubbleSort, the original array variable on the form is sorted (not a copy)
  • Copilot challenge: Ask Copilot to "add a Sub SelectionSort alongside BubbleSort and compare their pass counts in a Label"

Exercise 16.3 — Flexible Logger

  • Write Sub Log(level As String, message As String, Optional source As String = "App", Optional showTime As Boolean = True)
  • Write Sub LogMany(level As String, ParamArray messages() As String) that calls Log for each message
  • Display log entries in a ListBox with colour coding: ERROR = red, WARN = orange, INFO = blue
  • Add a Clear button that calls a ClearLog Sub which clears both the ListBox and a status Label
  • Copilot challenge: Ask Copilot to "add timestamps, save the log to a .txt file, and add a Sub ExportLog(path As String)"

Next: Lesson 17 — Functions

Learn how Functions differ from Sub procedures: they return a value. Master built-in functions and write your own with typed return values, recursion, and lambda expressions.

Continue »

Related Resources


Featured Books

Visual Basic 2022 Made Easy

Visual Basic 2022 Made Easy

by Dr. Liew Voon Kiong

Hands-on Sub procedure exercises including validation helpers, swap algorithms, and reusable UI utilities.

View on Amazon →
VB Programming With Code Examples

VB Programming With Code Examples

by Dr. Liew Voon Kiong

48 complete programs demonstrating modular design with well-named Sub procedures throughout.

View on Amazon →