🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA Sub in Excel — Procedures, Calling & Why Your Macro Is Just a Sub

|

VBA Sub in Excel — Procedures, Calling & Why Your Macro Is Just a Sub

Tested on: Excel 365 v2509 · Excel 2021 · Excel 2019 · last verified 2026-06-07

TL;DR — A Sub ("subroutine") is a named block of actions that runs when you call it and hands nothing back. The macro you run from the Macros dialog is a Sub. You call one by name — with or without the Call keyword:

Sub GreetUser()                 ' a Sub: a named set of actions, returns nothing
    MsgBox "Hello!"
End Sub

Sub RunReport()
    GreetUser                   ' call it — no Call, no brackets
    Call GreetUser              ' identical result, just the older Call style
End Sub

Get the calling style wrong and you hit "Compile error: Expected: =" or "Expected: end of statement" — the single most common Sub mistake. The rest of this guide is built around understanding why.

The mental model: a Sub is a verb you can call by name

Think of a Sub as a named action — a verb. FormatReport, ExportInvoices, ClearOldRows. You define the steps once, give them a name, and from then on you can run those steps from anywhere by saying the name.

The thing most tutorials never tell you: there is no separate "macro" type in VBA. When you record a macro or pick one from Developer → Macros, what you're running is a plain Sub — specifically a Public Sub with no arguments. "Macro" is just the friendly word Excel shows users; under the hood it's a Sub. Once that clicks, the whole VBA project stops feeling like magic and starts feeling like a list of named actions you can wire together.

This is the rule worth tattooing on the inside of your eyelids, because it explains almost every beginner compile error:

' No arguments — all three are fine:
GreetUser
Call GreetUser
Call GreetUser()

' WITH arguments — only these two are legal:
SaveLog "report.txt", 3        ' ✅ bare call: NO brackets, args separated by commas
Call SaveLog("report.txt", 3)  ' ✅ Call keyword: brackets REQUIRED

SaveLog("report.txt", 3)       ' ❌ Compile error: Expected: =

The last line fails because, without Call, VBA reads SaveLog( … ) as "evaluate SaveLog and use its return value" — but a Sub has no return value, and "report.txt", 3 isn't a single expression anyway. Brackets belong to Call, not to the bare call. It's the exact same logic as the MsgBox brackets rule: brackets show up only when a value is being used.

One sentence to remember: bare call → no brackets; Call keyword → brackets. Pick one style and stay consistent. Most modern VBA drops Call entirely.

A macro is a Public Sub — Private hides it

Whether your Sub appears in the Macros dialog comes down to one word in front of it:

Public Sub MonthlyClose()      ' shows in Alt+F8 Macros list — this is "a macro"
    PrepareData                ' calls a helper that the user never sees
    BuildSummary
End Sub

Private Sub PrepareData()      ' hidden from the Macros list — a helper only
    ' ...
End Sub

Public (the default for a plain Sub) means "anyone can call this, and show it to the user." Private means "only code in this module calls this." Use Private for the small helper Subs that do one step each — it keeps the Macros dialog clean and signals "this isn't an entry point." A Sub that takes arguments also drops out of the Macros list automatically, because Excel can't know what to pass it.

Passing data in: arguments

A Sub becomes reusable the moment it stops hard-coding values and starts accepting them:

Sub HighlightRow(targetRow As Long, colorIndex As Long)
    Rows(targetRow).Interior.ColorIndex = colorIndex
End Sub

Sub FlagOverdue()
    HighlightRow 5, 3          ' row 5 red
    HighlightRow 9, 6          ' row 9 yellow
End Sub

How those arguments travel — whether the called Sub can change the variable you passed — is a trap worth its own page. The default in VBA is ByRef, which means the original can be modified out from under you. See ByRef vs ByVal for the gotcha that explains "why did my counter change after I called that Sub?"

Event handlers are Subs — and must be

Every Worksheet/Workbook event you've ever written is a Sub, and it has no choice:

Private Sub Worksheet_Change(ByVal Target As Range)
    If Not Intersect(Target, Range("A:A")) Is Nothing Then
        MsgBox "Column A edited."
    End If
End Sub

Excel calls these when something happens (a cell changes, the book opens) and doesn't read a return value back, so they can only ever be Subs — never Functions. That asymmetry is the cleanest way to feel the Sub-vs-Function line: if the caller doesn't want an answer, it's a Sub.

The opinion: one Sub, one job

The fastest way to make VBA unmaintainable is the 300-line Sub Main() that imports, cleans, calculates, formats, and emails — all in one scroll. Break it into named Subs that each do one thing:

Public Sub RunPipeline()
    ImportData
    CleanData
    BuildReport
    EmailReport
End Sub

Now RunPipeline reads like a table of contents, each step is testable on its own, and when the email breaks you fix EmailReport without scrolling past 250 lines of unrelated code. A Sub name is documentation that can't go stale — it runs. This is the single habit that separates VBA that survives a year from VBA everyone is scared to touch.

Common Sub mistakes (and the fix)

Symptom Cause Fix
"Expected: =" when calling Brackets on a bare call: MySub(a, b) Drop brackets (MySub a, b) or add Call: Call MySub(a, b)
Macro doesn't appear in Alt+F8 Sub is Private, or it takes arguments Make it Public with no arguments, or call it from a no-arg wrapper
"Ambiguous name detected" Two Subs with the same name in one module Rename one — names must be unique per module
Sub runs but "nothing happens" Wrote a Sub where you needed a returned value Use a Function when the caller needs an answer
Argument changed unexpectedly Default ByRef let the Sub mutate your variable Declare the parameter ByVal — see ByRef vs ByVal
"Sub or Function not defined" Typo, or the Sub lives in another module/workbook Check spelling; qualify with the module/workbook if needed

When the macro itself is the hard part — describe it instead

Splitting work into clean Subs is good engineering, but it's still plumbing you have to write and maintain. When the logic is "import these three files, match them on order ID, flag mismatches, email finance," the real work isn't the Sub boundaries — it's all the code inside them. ExcelMaster Agent lets you say that in plain English and writes the whole pipeline for you — no Sub, no Call, no maintenance. Try it free →

FAQ

What is a Sub in VBA? A Sub (subroutine) is a named block of VBA code that performs actions and returns no value. You define it with Sub Name() … End Sub and run it by calling its name. The macros you run from Excel's Macros dialog are Subs.

What's the difference between a Sub and a macro? There is no technical difference. A "macro" is just the user-facing name for a Public Sub that takes no arguments — the only kind that shows up in the Developer → Macros (Alt+F8) list. Every macro is a Sub; not every Sub is shown as a macro.

How do I call a Sub in VBA? Two ways, both equivalent: write the name on its own — MySub (or MySub arg1, arg2 with arguments, no brackets) — or use the Call keyword — Call MySub(arg1, arg2) (brackets required). Don't put brackets on a bare call with multiple arguments; that's a compile error.

Why does MySub(a, b) give a compile error? Without the Call keyword, VBA treats the brackets as "use this Sub's return value," but a Sub has no return value, and a, b isn't a single expression. Either remove the brackets (MySub a, b) or add Call (Call MySub(a, b)).

What is the difference between Sub and Function in VBA? A Sub does work and returns nothing; a Function does work and returns a value to whoever called it. Use a Function when the caller needs an answer back (and when you want to use it as a worksheet formula); use a Sub for everything else.

What does Private Sub mean in VBA? Private Sub makes the procedure callable only from within the same module and hides it from the Macros dialog. Use it for helper procedures that aren't meant to be run directly by the user. Event handlers like Worksheet_Change are Private Sub for this reason.