I've been using VBA for over 15 years — in finance, consulting, and teaching — and it's the single Excel skill that has saved me the most time. Not marginally. Dramatically.
The problem is that most VBA guides throw definitions and syntax at you before you've had a chance to care. So let me show you the value first, and the mechanics second.
Three real examples of what VBA actually does for you:
This guide teaches you how to do exactly this — from your very first line of code to writing macros that actually solve real problems at work. I've written it the way I wish someone had taught me: practical first, theory second, no unnecessary jargon.
Let's start with the question everyone asks first.
VBA vs Python vs Office Scripts vs Power Automate — Which Should You Learn?
Before you invest time in learning VBA, it's worth understanding how it compares to the alternatives. I get asked this question constantly, so here's my honest breakdown.
The short answer: if you work in desktop Excel and want to automate things fast, VBA is still your best option in 2026. But the longer answer depends on what you're trying to do.
Tool | Best for | Learning curve | Excel access | Cloud / Online | Cost |
|---|---|---|---|---|---|
VBA | Desktop Excel automation, macros, custom functions | Low — beginner friendly | Full — every object, property, event | Desktop only — no Excel Online | Free — built into Excel |
Python | Large datasets, data science, automation outside Excel | Medium — more to set up | Good via openpyxl / xlwings, but indirect | Yes — runs anywhere | Free — open source |
Office Scripts | Excel Online automation, sharing scripts across teams | Medium — requires JavaScript knowledge | Good — but fewer features than VBA | Yes — built for the web | Requires Microsoft 365 Business plan |
Power Automate | Workflow automation across apps (not just Excel) | Low — no-code / low-code | Limited — works on files, not cells | Yes — cloud native | Requires Microsoft 365 subscription |
My personal take on when to use each:
Right, let's get into it. Here's exactly what VBA is and how it thinks.
What You Need Before Starting
Good news — the bar to get started with VBA is low. You don't need a programming background, a special version of Excel, or any paid tools. Here's the full checklist:
That's everything you need. Let's now look at what VBA actually is under the hood — because once you understand how it thinks, writing code becomes much more intuitive.
What is VBA?
VBA stands for Visual Basic for Applications. It's a programming language that Microsoft built directly into Excel — and into Word, PowerPoint, Access, and Outlook too. You don't download it, you don't install it. It's already there, waiting for you to use it.
At its core, VBA is how you give Excel instructions. Instead of clicking buttons and dragging cells yourself, you write a set of instructions — called a macro — and Excel follows them automatically, as many times as you need, at whatever speed your computer can handle.
VBA is an Object-Oriented Language — here's what that means
You'll hear the term "object-oriented" a lot with VBA. It sounds technical, but the idea is simple. Think of it like giving instructions to someone in English:
If you wanted to tell someone to open a box, you'd say: "Open the box."
In VBA, that same instruction looks like this:
Notice the pattern: Object ? dot ? Action. That dot is everything in VBA. It connects an object (the thing you're working on) to either a method (something you want it to do) or a property (something you want to get or set about it).
Here's the key distinction:
The three Excel objects you'll use most
Excel has hundreds of objects, but when you're starting out, three cover probably 90% of everything you'll want to do. Here's each one with a real code example:
Those three objects sit inside each other like Russian dolls — and that nesting is what makes VBA so logical once it clicks:
Now let's get your hands on the keyboard. First, we need to set up Excel so you can actually write and run code.
Setting Up Excel for VBA
Before you can write or run any VBA code, you need to do two things: enable the Developer tab in Excel, and get familiar with the Visual Basic Editor (VBE). Neither takes more than a few minutes.
Step 1 — Enable the Developer tab
The Developer tab is where all the VBA controls live — the button that opens the editor, the macro recorder, and more. It's hidden by default, so here's how to switch it on:
File > Options > Customize Ribbon > Developer > OK
Excel menu ? Preferences ? Ribbon & Toolbar ? Developer ? Save
Step 2 — A tour of the Visual Basic Editor (VBE)
The Visual Basic Editor is where you write, edit, and run all your VBA code. When you open it for the first time it can look intimidating — lots of windows and panels. But you only need to know four areas:
Name Module1
Sub MyFirstMacro()
'This is a comment
Range("A1").Value = "Hello!"
End Sub
? Range("A1").Value
Hello!
You're set up. Now let's write some actual code. We'll start by recording a macro — which is the fastest way to understand VBA syntax before you write it yourself.
Record Your First Macro — Before Writing One
I always teach recording before writing. Here's why: the macro recorder watches everything you do in Excel and converts it into VBA code automatically. This means you can see real, working VBA syntax before you've written a single line yourself. It's one of the best learning tools in Excel, and most people never use it this way.
Let's record a simple macro that bolds a cell and changes its background colour. Nothing complex — the point is to see what VBA looks like when Excel writes it.
Steps to record your first macro
Your recorded macro will look something like this:
Don't worry if it looks messy — recorded code always is. But look at what you can already read: Range("A1") is the cell, Font.Bold = True makes it bold, Interior.Color sets the fill. That's real VBA, and you already understand the structure from the last section.
Two things the recorder teaches you that no tutorial mentions
Now that you know how VBA code is structured, let's write one from scratch — properly, this time.
Write Your First Macro from Scratch
Now it's time to write code yourself — not record it, not copy it. The best way to learn VBA is to write more code from scratch, even when it feels slow at first. The muscle memory builds faster than you'd expect.
Here's how you open the editor and create a module to write in:
Let's write three macros — each one slightly more useful than the last.
The simplest possible macro — write a value into a specific cell. This teaches you the core pattern: object ? property ? value.
Line of code | What it does |
|---|---|
Sub EnterValue() | Starts the macro and gives it the name EnterValue. The empty brackets mean it takes no inputs. |
Range("A1").Value | Refers to cell A1 and targets its Value property — the content of the cell. |
= "Done" | Sets that value to the text "Done". Text always goes inside double quote marks. |
= 1000 | Sets the value to the number 1000. Numbers don't need quote marks. |
= Date | Date is a built-in VBA function that returns today's date. No quote marks needed. |
End Sub | Closes the macro. VBA stops executing here. |
This one does something genuinely useful — it scans a column and highlights every blank cell in red. We haven't formally covered loops yet, but I want you to see one in action early. Don't worry about fully understanding every line — focus on the overall pattern.
Line of code | What it does |
|---|---|
Dim i As Long | Creates a variable called i that stores whole numbers. We use it as a row counter. |
Cells(Rows.Count, 1).End(xlUp).Row | The standard way to find the last row with data in column 1 (column A). Works regardless of how many rows you have. |
For i = 1 To lastRow | Starts a loop — run the code inside for every value of i from 1 up to lastRow. |
Cells(i, 1) | Refers to the cell in row i, column 1 (A). As i increases, we move down the column. |
If ... = "" Then | Checks if the cell is empty. Double quotes with nothing between them means empty string. |
Interior.Color = RGB(...) | Sets the background fill colour using red, green, blue values. RGB(255,0,0) is pure red. |
Next i | Increases i by 1 and goes back to the top of the loop. |
This one I use constantly. Instead of manually typing today's date into a filename every time I save a report, this macro does it automatically and saves the file to a folder I specify. It's small, but it shows how VBA can eliminate even the most routine tasks.
Line of code | What it does |
|---|---|
Dim fileName As String | Creates a variable to hold the filename text. String means it stores text. |
Format(Date, "YYYY-MM-DD") | Takes today's date and converts it to a text string in the format 2026-04-04. Without Format(), the date looks different depending on your regional settings. |
& | The ampersand joins text strings together. "Sales Report - " & "2026-04-04" & ".xlsm" becomes "Sales Report - 2026-04-04.xlsm". |
savePath | The folder where the file saves. Change this to any folder path on your computer. |
ThisWorkbook.SaveAs | Saves the current file with a new name. Different from .Save which overwrites the existing file. |
MsgBox "Saved as: " & fileName | Shows a pop-up message confirming the save. Useful to add to any macro that makes changes you can't easily see. |
Working with Ranges and Cells
The Range object is the most-used object in all of VBA. If you're automating anything in Excel, you're almost certainly reading from or writing to a range of cells. Getting comfortable with Range is the single biggest skill jump you can make as a VBA beginner.
There are three ways to refer to a cell or group of cells in VBA, and knowing when to use each one will save you a lot of frustration.
Three ways to reference a cell — Range, Cells, and ActiveCell
These three blocks of code do exactly the same thing — write "Hello" into cell C4. The syntax is what differs.
Reference | What it means |
|---|---|
Range("C4") | Column C, row 4. Always the column letter first, then the row number — same as Excel. |
Cells(4, 3) | Row 4, column 3. Note the order is reversed from Range — rows first, then columns. Column 3 = C. |
Range("A1:A10").Value = "Filled" | Writes the same value into all 10 cells at once. VBA fills the entire range in a single line. |
Reading a value from a cell is just as important as writing one. Here's a practical example — read a price and quantity from the sheet, calculate the total, and write it back.
Line of code | What it does |
|---|---|
price = Range("B2").Value | Reads whatever number is in B2 and stores it in the variable price. The cell is the source; the variable is the destination. |
(price * quantity) * (1 - discount) | Calculates the total after applying the discount percentage. If discount is 0.1 (10%), then (1 - 0.1) = 0.9, meaning 90% of the full price. |
Range("E2").NumberFormat = "$#,##0.00" | Applies currency formatting to the result cell — the same as formatting a cell manually in Excel. The format string is identical to Excel's custom number format syntax. |
This is the pattern I use in almost every macro I write. The problem with hardcoding a range like Range("A1:A100") is that your data changes — some days 50 rows, some days 200. The LastRow pattern finds the actual last row of data dynamically, every time.
Line of code | What it does |
|---|---|
Rows.Count | Returns the total number of rows in the worksheet (1,048,576 in modern Excel). This gets us to the very bottom of the sheet. |
Cells(Rows.Count, 1) | The cell in the very last row of column 1 (column A) — which is always empty if your data doesn't fill the entire sheet. |
.End(xlUp).Row | From that empty bottom cell, move up until you hit a cell with data — exactly like pressing Ctrl+Up. .Row then gives you the row number of that cell. |
"A1:A" & lastRow | Builds the range address as a string. If lastRow is 150, this produces "A1:A150" — the exact range of your data every time. |
Set dataRange = Range(...) | When assigning an object (like a Range) to a variable, you must use the Set keyword. Forgetting Set is one of the most common VBA errors for beginners. |
Offset lets you move a set number of rows and columns from a starting cell. It's incredibly useful when you're processing data row by row and need to read from or write to cells adjacent to your current position.
Offset syntax | What it means |
|---|---|
Offset(1, 0) | Move 1 row down, stay in the same column. From A1, this gives A2. |
Offset(0, 1) | Stay in the same row, move 1 column right. From A1, this gives B1. |
Offset(-1, 0) | Move 1 row up (negative numbers go backwards). From A3, this gives A2. |
Offset(3, 2) | Move 3 rows down and 2 columns right. From A1, this gives C4. |
Working with Worksheets and Workbooks
Most real automation touches more than one sheet or more than one file. A macro that only works on the active sheet is fragile — move the wrong window to the front and it runs on the wrong data. Understanding how to reference sheets and workbooks explicitly is what separates reliable macros from ones that randomly do the wrong thing.
Three ways to reference a worksheet — and which to use
Reference | What it means | Risk level |
|---|---|---|
ActiveSheet | Whichever sheet is visible in Excel when the macro runs. Changes depending on what the user clicked last. | High — unpredictable |
Worksheets(1) | The first sheet by tab position. Changes if someone reorders the tabs. | Medium — fragile |
Worksheets("Sales") | The sheet named "Sales". Only breaks if someone renames the tab. | Low — use this |
ThisWorkbook.Worksheets("Sales") | The sheet named "Sales" in the workbook containing the VBA code — regardless of what's active. | Lowest — use this |
ThisWorkbook vs ActiveWorkbook — the distinction that matters most
ThisWorkbook always refers to the workbook that contains the VBA code you're running. ActiveWorkbook refers to whichever workbook is in focus in Excel at that moment — which could be anything. If your macro opens another file to read data from it, ActiveWorkbook immediately switches to that file. Any code after that which uses ActiveWorkbook is now running on the wrong file.
Here are the worksheet operations you'll use most often, with safety checks built in. Deleting a sheet without a warning is the kind of thing that makes colleagues very unhappy.
Line of code | What it does |
|---|---|
Worksheets.Add(After:=...) | Adds a new blank sheet. Without the After argument, it adds before the active sheet. Using After:= with the last sheet index adds it at the end — usually what you want. |
ws.Name = "Monthly Summary" | Renames the sheet. Will throw an error if a sheet with that name already exists — add a check if you're running this repeatedly. |
Visible = False | Hides the sheet — it still exists and can be referenced in code. The user can unhide it manually. To truly hide it from the user, use xlVeryHidden instead of False. |
Application.DisplayAlerts = False | Suppresses Excel's "Are you sure you want to delete this sheet?" dialog. Always set it back to True immediately after — leaving it False means Excel will silently suppress other alerts too. |
.Copy After:=... | Copies the sheet to the position after the last tab. The copied sheet keeps all data, formatting, and formulas from the original. |
This is a pattern I use constantly — open an external file, pull data from it, paste it into my workbook, then close the external file without saving. It replaces the manual copy-paste that most people do every morning with their source files.
Line of code | What it does |
|---|---|
Workbooks.Open(filePath) | Opens the file at the given path and returns a Workbook object. Assign it to a variable immediately — this is the only reliable way to reference it after it opens. |
sourceWB.Worksheets("Sheet1") | References the sheet by name in the source workbook — not in ThisWorkbook. Always prefix the sheet reference with the workbook variable when working across multiple files. |
ThisWorkbook.Worksheets("Import") | The destination sheet in your own file. ThisWorkbook keeps this anchored correctly even though a second workbook is now open. |
PasteSpecial xlPasteValues | Pastes values only — not formulas, not formatting. This avoids broken formula references when copying between workbooks and is almost always what you want for data imports. |
sourceWB.Close SaveChanges:=False | Closes the source file without saving. Always close files you open programmatically — leaving them open wastes memory and confuses users who find mystery files open in their taskbar. |
This combines loops, worksheets, and workbook operations into one practical macro — something I've used in reporting workflows to distribute monthly reports automatically. Each sheet gets exported as its own PDF named after the tab.
Line of code | What it does |
|---|---|
If ws.Visible = True Then | Skips hidden and very-hidden sheets. A hidden sheet is usually hidden intentionally — exporting it would expose data that wasn't meant to be shared. |
ws.Name & " - " & Format(Date...) | Builds a filename like "Sales - 2026-04-04.pdf". Combining the sheet name with the date means multiple runs don't overwrite each other. |
ExportAsFixedFormat | Excel's built-in PDF export method. Works on sheets, ranges, or entire workbooks. xlTypePDF specifies PDF format — you can also use xlTypeXPS for the XPS format. |
exported = exported + 1 | Counts how many sheets were successfully exported. The final MsgBox uses this to confirm the result without the user having to count tabs manually. |
Variables and Data Types
A variable is a named container that holds a value while your code is running. Instead of hardcoding the same number or text in five different places in your macro, you store it in a variable once and reference that variable everywhere. Change the variable, and everything updates automatically.
Think of it like a labelled box. You put something in the box, give the box a name, and whenever you need that thing you just ask for the box by name — you don't have to remember exactly what's inside it.
How to declare a variable
You declare a variable using the Dim statement — short for "Dimension." The syntax is always the same:
Data types — which one should you use?
Declaring the right data type matters because it tells VBA how much memory to allocate and what kind of values the variable can hold. Here are the ones you'll use most often:
Data type | What it stores | When to use it |
|---|---|---|
String | Text of any length | Names, file paths, cell content that's text |
Long | Whole numbers up to ~2 billion | Row counts, counters in loops — use this instead of Integer |
Integer | Whole numbers up to 32,767 | Rarely needed — Long covers everything Integer does and more |
Double | Decimal numbers | Prices, percentages, calculations with fractions |
Boolean | True or False only | Flags, toggles, condition results |
Date | Date and time values | Any date maths or date comparisons |
Variant | Anything — VBA decides at runtime | Avoid unless you genuinely don't know the type in advance |
The simplest way to see variables in action — store a few values and use them to write into cells.
Line of code | What it does |
|---|---|
Dim employeeName As String | Creates a variable called employeeName that can only hold text. |
Dim salary As Double | Creates a variable for decimal numbers — salary can be 52000.50, not just 52000. |
Dim isActive As Boolean | Creates a variable that can only be True or False. |
employeeName = "Priya Sharma" | Assigns the text "Priya Sharma" to the variable. Now wherever you write employeeName, VBA substitutes "Priya Sharma". |
Range("A1").Value = employeeName | Writes the contents of employeeName into cell A1 — no need to type the name again. |
This is where variables start to earn their keep. Instead of hardcoding numbers in a formula, we store them in variables — making the code readable and easy to update.
Scope controls which parts of your code can see and use a variable. Get this wrong and you'll spend a lot of time wondering why your variable is empty when you're sure you set it.
Concept | What it means |
|---|---|
Dim at top of module | reportTitle is declared outside any Sub, so it's visible to both SetTitle() and UseTitle(). Run SetTitle first, then UseTitle — the value carries over. |
Dim inside a Sub | If reportTitle were declared inside SetTitle(), UseTitle() would not be able to see it — it would be empty (an empty string for String variables). |
Public keyword | Use Public instead of Dim at the top of a module to make the variable accessible from any module in the project. |
Always use Option Explicit
Add this one line to the very top of every module you write, before any Subs:
Conditions — IF Then Else and Select Case
Conditions are what make your macros smart. Without them, a macro does the same thing every time it runs regardless of what's in your data. With conditions, it can make decisions — "if this cell is blank, do X; otherwise, do Y." That's the difference between a recording and a real program.
VBA gives you two ways to write conditions: IF Then Else and Select Case. They're not interchangeable — each has situations where it's the cleaner choice.
The IF Then Else structure
Comparison and logical operators
Operator | Meaning | Example |
|---|---|---|
= | Equal to | If score = 100 Then |
Not equal to | If status "Done" Then | |
> | Greater than | If sales > 50000 Then |
Less than | If age | |
>= | Greater than or equal | If score >= 90 Then |
Less than or equal | If quantity | |
And | Both conditions must be True | If age > 18 And age |
Or | At least one condition must be True | If dept = "Sales" Or dept = "Marketing" Then |
Not | Reverses True/False | If Not IsEmpty(Range("A1")) Then |
A practical macro that checks a sales figure and labels it as either above or below target — then colours the cell to match.
Line of code | What it does |
|---|---|
If salesAmount >= target | Checks if salesAmount is greater than or equal to target. If True, runs the indented block below it. |
ElseIf salesAmount >= target * 0.8 | Only checked if the first condition was False. target * 0.8 is 80% of the target — so this catches "nearly there" results. |
Else | Catches everything that didn't meet either condition above. No condition needed — it's the fallback. |
RGB(198, 239, 206) | A soft green — the same colour Excel uses for its built-in "Good" conditional formatting. Match your VBA colours to Excel's native palette for consistency. |
Real decisions often depend on more than one thing at a time. Here's a macro that checks both a score and an attendance record before assigning a grade — because passing isn't just about the test.
Select Case is an alternative to a long chain of ElseIf statements. When you're testing the same variable against many possible values, Select Case is dramatically cleaner. Here's the same grade problem, but now we're assigning a department code based on a region name.
code = "N"
ElseIf region = "South" Then
code = "S"
ElseIf region = "East" Then
code = "E"
ElseIf region = "West" Then
code = "W"
End If
Case "North": code = "N"
Case "South": code = "S"
Case "East": code = "E"
Case "West": code = "W"
Case Else: code = "?"
End Select
Select Case syntax | What it matches |
|---|---|
Case 1 | Exactly the value 1. Equivalent to If salesRank = 1. |
Case 2 To 5 | Any value from 2 to 5 inclusive. This is where Select Case shines — IF can't do this in one line. |
Case Is > 10 | Any value greater than 10. The Is keyword lets you use comparison operators inside a Case. |
Case Else | Catches anything not matched by the Cases above — the equivalent of Else in an IF block. Always include it to handle unexpected input. |
Exit Sub | Stops the macro immediately. Used here to bail out early if the input is invalid, before writing anything to the sheet. |
Loops — For Next, For Each, Do While
Loops are where VBA's real power becomes undeniable. A loop lets you run the same block of code repeatedly — once for every row in your data, once for every sheet in your workbook, once for every file in a folder. What would take you an hour of manual work, a loop handles in under a second.
There are three loop types you need to know. Each suits a different situation:
This is the loop you'll write most often. It steps through every row in your data and does something to each one. Here it reads a sales value, checks it against a target, and colours the row accordingly — combining everything from the last two blocks.
Line of code | What it does |
|---|---|
For i = 2 To lastRow | Starts i at 2 (skipping row 1 header) and increments it by 1 each iteration until it reaches lastRow. Each pass through the loop, i holds the current row number. |
Cells(i, 2).Value | Because i changes each loop, this reads a different row every time. When i = 2 it reads B2, when i = 3 it reads B3, and so on. This is exactly why Cells() beats Range() inside loops. |
Next i | Increases i by 1 and jumps back to the top of the loop. When i exceeds lastRow, the loop ends and VBA moves to the next line after Next i. |
(lastRow - 1) | Subtracts 1 from lastRow to get the correct row count — because we started at row 2, not row 1. |
For Each is cleaner than For Next when you're processing a collection of objects rather than a numbered sequence. You don't need a counter, you don't need to know how many items there are — VBA handles it automatically.
Line of code | What it does |
|---|---|
Dim ws As Worksheet | Declares ws as a Worksheet object variable. Each iteration of the loop, ws is set to the next sheet in the workbook. |
For Each ws In ThisWorkbook.Worksheets | Iterates through every sheet in the current workbook. No counter needed — VBA moves to the next sheet automatically after each pass. |
ws.Range("A1") | Using ws. prefix means we're always working on the current sheet in the loop — not the active sheet. This is important: never use ActiveSheet inside a For Each loop. |
ws.Name | The Name property of the worksheet object — the tab name. Using it in the header value means each sheet gets its own name in the title automatically. |
ThisWorkbook.Worksheets.Count | Returns the total number of sheets. Used in the final message to confirm how many were processed. |
Do While is condition-driven. Instead of saying "loop 100 times," you say "keep looping as long as this condition is True." It's the right choice when you're processing data of unknown length or waiting for a specific value to appear.
Line of code | What it does |
|---|---|
Do While ... "" | Checks the condition before each iteration. If column A is empty, the loop never starts. If it becomes empty mid-loop, the loop stops immediately at the top of the next iteration. |
currentRow = currentRow + 1 | This line is critical — without it, the loop never moves forward and runs forever. Always make sure your Do While loop has something that changes the condition. |
UCase(cellValue) | A built-in VBA function that converts text to uppercase. VBA has equivalents for most Excel text functions: UCase, LCase, Trim, Len, Left, Right, Mid. |
Loop | Marks the end of the Do While block. VBA jumps back to the Do While line and checks the condition again. |
Sometimes you don't need to process every row — you just need to find the first row that meets a condition and stop. Exit For lets you break out of a loop the moment your work is done, rather than wasting time checking every remaining row.
Line of code | What it does |
|---|---|
foundRow = 0 | Initialises foundRow to 0 before the loop. After the loop, if foundRow is still 0, we know nothing was found. This is a common pattern for tracking whether a search succeeded. |
Exit For | Immediately stops the loop and jumps to the line after Next i. Without it, the loop would continue checking every remaining row even after finding the first match — wasting time on large datasets. |
If foundRow > 0 Then | After the loop, check whether we found anything. foundRow > 0 means the loop set it to a real row number. foundRow = 0 means the condition was never met. |
Error Handling — Stop Your Code from Crashing
Every macro you write will eventually hit an error on real-world data. A file won't be found. A sheet won't exist. A cell will contain text when your code expected a number. Without error handling, VBA stops dead and shows the user a cryptic debug dialog. With it, your macro handles the problem gracefully — logs what went wrong, cleans up after itself, and tells the user something useful.
Error handling is not optional in production macros. It's the difference between code that works in testing and code that's safe to hand to someone else.
The four types of VBA errors
The On Error statement — three modes
This is the skeleton every error-handled macro follows. The handler section at the bottom catches any runtime error, reads what went wrong from the Err object, and tells the user something useful before exiting cleanly.
Line of code | What it does |
|---|---|
On Error GoTo ErrorHandler | Tells VBA: if any runtime error occurs after this line, jump immediately to the label called ErrorHandler. The label can be any name — just keep it consistent. |
Exit Sub | Critical — without this, VBA falls through into the error handler section even when no error occurred. Exit Sub ensures the handler only runs when something actually went wrong. |
ErrorHandler: | A label — a named location in the code. VBA jumps here when an error occurs. The colon at the end is what makes it a label, not a variable name. |
Err.Number | The error number VBA assigns to the error that occurred. Each runtime error has a unique number — e.g. Error 9 = Subscript out of range, Error 1004 = Application-defined error. |
Err.Description | A plain English description of the error. Showing this to the user is much more helpful than showing the error number alone. |
If Not wb Is Nothing Then | Checks whether the workbook was successfully opened before the error occurred. If the error happened on the Open line itself, wb is Nothing — closing Nothing would cause another error. |
On Error Resume Next gets a bad reputation because people leave it on for entire macros, silently swallowing every error. Used correctly — for a single operation, then immediately reset — it's genuinely useful. The classic use case: checking if a sheet exists without crashing.
Line of code | What it does |
|---|---|
On Error Resume Next | Tells VBA to silently ignore any error on the next line. If the sheet doesn't exist, the Set line fails — but instead of crashing, VBA moves to the next line with ws still set to Nothing. |
On Error GoTo 0 | Immediately resets error handling. This is placed on the very next line after the risky operation. Everything after this line will crash normally on errors — which is what you want. |
If ws Is Nothing Then | After the risky operation, check whether it worked. If ws is Nothing, the sheet didn't exist and the Set failed silently. If ws is not Nothing, the sheet was found and set correctly. |
This is the template I paste into every macro I write that touches real data. It handles cleanup, restores Excel settings that the macro might have changed, and gives the user a clear message. Copy it and adapt it — you'll use this pattern for the rest of your VBA career.
Line of code | What it does |
|---|---|
Application.Calculation = xlCalculationManual | Stops Excel recalculating formulas after every cell change. On sheets with thousands of formulas this can make macros 10x faster. Always restore to xlCalculationAutomatic at the end. |
CleanUp: | A second label that handles cleanup code. Both the success path (Exit Sub after cleanup) and the error path (Resume CleanUp) pass through here — so Excel settings are always restored regardless of whether an error occurred. |
Erl | Returns the line number where the error occurred — if you've numbered your lines. Without line numbers, Erl returns 0. It's optional but useful when debugging complex macros. |
Resume CleanUp | After the error handler shows the message, jump to CleanUp to restore ScreenUpdating, Calculation, and DisplayAlerts before exiting. Without this, those settings stay off even after the macro crashes. |
Arrays — Handle Large Datasets Fast
An array is a variable that holds multiple values instead of just one. Think of a regular variable as a single labelled box — it holds one thing. An array is a row of numbered boxes — it can hold hundreds or thousands of values, all accessible by their position number.
Arrays matter for one reason above all others: speed. Reading from and writing to Excel cells inside a loop is slow — each cell interaction requires VBA to communicate with the Excel object model. Reading a range into an array first, processing it entirely in memory, then writing the results back in one shot can make your macros 10 to 100 times faster on large datasets. Once you understand this pattern, you'll use it everywhere.
Static vs dynamic arrays
ReDim scores(1 To lastRow) Size determined after you know lastRow.
Before using the performance pattern in Example 2, you need to understand the fundamentals — how to declare an array, put values in, and read them back out.
Line of code | What it does |
|---|---|
Dim months(1 To 12) As String | Declares a static array with 12 slots, indexed 1 through 12. The 1 To syntax is explicit — without it, VBA defaults to starting at 0, which is confusing when your data starts at row 1. |
LBound(months) | Returns the lowest index of the array — 1 in this case. Using LBound and UBound instead of hardcoded numbers makes your loop work correctly even if you change the array size later. |
UBound(months) | Returns the highest index — 12 here. For a dynamic array, UBound gives you the current size after ReDim has been called. |
ReDim scores(1 To count) | Sets the size of a dynamic array at runtime. Can be called again to resize — but resizing with ReDim alone wipes the existing values. Use ReDim Preserve to keep existing values when resizing. |
Erase scores | Clears the array and frees the memory it used. For large arrays on tight memory systems, always Erase when you're done. |
This is the most important performance pattern in VBA. Instead of reading and writing cells one at a time inside a loop — which forces VBA to interact with the Excel object model on every iteration — you load the entire range into an array in one read, process everything in memory, then write the results back in one write. The difference on large datasets is dramatic.
Line of code | What it does |
|---|---|
dataArr = ws.Range("A2:B" & lastRow).Value | Loads the entire two-column range into a 2D Variant array in a single operation. The array is automatically sized to match the range — no ReDim needed. dataArr(row, col) maps directly to the range rows and columns. |
ReDim resultArr(1 To lastRow - 1, 1 To 1) | Creates the output array with the same number of rows as the input (lastRow - 1 because data starts at row 2) and 1 column for results. The second dimension (1 To 1) means one column. |
UBound(dataArr, 1) | The second argument to UBound specifies which dimension — 1 for rows, 2 for columns. For a 2D array loaded from a range, dimension 1 is rows and dimension 2 is columns. |
dataArr(i, 2) | Reads column B of the input range (column index 2). The array is 1-based because the range was loaded with .Value — so row 1 of the array = row 2 of the spreadsheet (the first data row). |
ws.Range("C2:C" & lastRow).Value = resultArr | Writes the entire result array back to the sheet in a single operation. This is the other half of the performance gain — one write instead of thousands. |
Using AI to Write VBA — ChatGPT and Copilot
AI tools have changed how people write VBA. ChatGPT, Microsoft Copilot, and similar tools can generate working macro code from a plain English description in seconds — saving hours of typing, googling, and debugging. I use them regularly. But there's a right way and a wrong way to use them, and if you don't understand what the code is doing, AI becomes a liability rather than an asset.
Here's how to get the most out of AI for VBA — and where it will let you down.
The two main tools and what each is good at
How to write a good VBA prompt
The quality of AI-generated VBA code is almost entirely determined by the quality of your prompt. Vague prompts produce vague, generic code that needs rewriting. Specific prompts produce code you can use immediately with minor adjustments.
The formula for a strong VBA prompt is: what sheet + which columns + what condition + where the result goes + which VBA patterns to use + how to handle errors. You don't need all of these every time — but the more you provide, the less rewriting you'll do.
How to debug AI-generated VBA code
AI code almost always works as a starting point, but rarely works perfectly on first run on your specific data. Here's the process I use every time:
What AI cannot do for you in VBA
What VBA Cannot Do — Honest Limitations
VBA is powerful, but it has real limits. Knowing these upfront saves you from building something that works in testing and then fails in production, or promising a colleague something VBA simply can't deliver.
VBA Best Practices
These are the habits that separate code that works from code that's reliable, readable, and maintainable. Most of them take almost no extra time to apply, they're just decisions about how you write, name, and structure things. I've been following these rules for almost a decade and they've saved me far more time in debugging than they've ever cost me in writing.
Application.Calculation = xlCalculationManual
Before and after — the same macro, written badly and well
Here's a real example of what applying these rules looks like in practice. Same logic, same result — completely different readability.
Same number of logical operations. But the "after" version has: Option Explicit, descriptive variable names, indentation, comments explaining each block, ScreenUpdating, and an error handler. Anyone on your team — including you in six months — can open that second version and understand it immediately.
Frequently Asked Questions
These are the questions I get asked most often — by students, by colleagues, and in comments on ExcelChamps. Answered here as directly as I can.
Is VBA still relevant in 2026 — or is it dying?
VBA is not dying. It's been declared dead every few years since Python became popular, and it's still running inside hundreds of millions of Excel files in businesses worldwide. Microsoft has not deprecated it and has no announced plans to do so.
What is true is that VBA is no longer the only option. Office Scripts, Python in Excel, and Power Automate have expanded what's possible. But for automating desktop Excel on Windows — which is still the dominant use case in most organisations — VBA remains the fastest, most direct, and most capable tool available. No setup required, no licences, no dependencies. It's already there.
My honest assessment: VBA will remain useful and in demand for at least another 10 years. If you use Excel daily and you don't know VBA, learning it is still one of the highest-return skills you can add.
VBA vs Python — which should I learn for Excel automation?
This depends entirely on what you're trying to do. Here's the honest breakdown:
- Learn VBA if you want to automate desktop Excel tasks quickly, you have no programming background, and you need results within days not weeks. VBA is purpose-built for Excel and delivers the fastest path from zero to useful automation.
- Learn Python if you work with very large datasets (hundreds of thousands of rows), need to connect Excel to databases or APIs, want to do data science or machine learning, or want a programming skill that's useful outside of Excel entirely.
They're not mutually exclusive. I use both. Many experienced Excel developers start with VBA, build their programming intuition, then add Python when they need it. VBA teaches you the logic of programming — variables, loops, conditions, objects — which makes Python much easier to pick up later.
How long does it take to learn VBA?
Honestly — faster than most people expect. Here's a realistic timeline based on what I've seen teaching VBA over the years:
- Week 1–2: You can write simple macros that enter data, format cells, and loop through rows. Enough to save time on small tasks.
- Month 1–2: You understand variables, conditions, loops, and basic error handling. You can automate most repetitive Excel tasks you encounter at work.
- Month 3–6: You're comfortable with UDFs, working across multiple files, arrays, and can write macros that others at your organisation can use reliably.
- 6 months+: Advanced topics — class modules, APIs, complex automation systems, professional-grade error handling and logging.
The fastest way to progress is to have a real problem to solve. Pick something you actually do manually every week at work and automate it. Learning with a purpose is ten times faster than working through abstract exercises.
Does VBA work on Excel for Mac?
Yes, VBA works on Excel for Mac — but with limitations that matter for some use cases:
- UserForms (pop-up dialog boxes) are not supported on Mac
- Windows API calls using Declare do not work on Mac
- File paths use forward slashes on Mac (/) vs backslashes on Windows (\) — use Application.PathSeparator to write cross-platform code
- The VBA Shell function behaves differently on Mac (Unix commands vs Windows commands)
Everything covered in this guide works on Mac unless specifically noted. If you're writing macros that will be shared between Windows and Mac users, test on both platforms and avoid Windows-specific functions.
Can VBA run in Excel Online?
No. VBA macros do not run in Excel Online — the browser-based version of Excel. The VBA engine only exists in the desktop application.
If you open a .xlsm file in Excel Online, the file opens but macros are disabled — there's no way to run them from the browser. You need to download the file and open it in the desktop app.
Microsoft's answer for web-based Excel automation is Office Scripts — a JavaScript-based scripting tool built into Excel Online. If your organisation is cloud-first and works predominantly in Excel Online, Office Scripts is worth exploring. It has its own learning curve but the concepts (loops, conditions, ranges) will feel familiar once you know VBA.
Do I need coding experience to learn VBA?
No — and I mean that without qualification. VBA is one of the most beginner-friendly programming languages precisely because it was designed for non-programmers. Every object maps to something you already know from using Excel. A Worksheet is a tab. A Range is a cell or group of cells. A Workbook is a file.
What does help is a solid understanding of Excel itself — formulas, basic functions, understanding what rows and columns are. If you can write a VLOOKUP, you have everything you need to start learning VBA.
I've taught VBA to accountants, HR managers, teachers, and project coordinators who had never written code before. Within two weeks, all of them were writing macros that saved them real time at work. The learning curve is real but it's not steep.
Can VBA interact with other Microsoft Office applications?
Yes — this is one of VBA's most powerful and underused features. Because VBA is built into all Office applications, you can write Excel macros that control Word, Outlook, PowerPoint, and Access directly.
Some practical things people build with cross-application VBA:
- Send personalised emails from Outlook using data from an Excel sheet
- Generate Word reports from Excel data, with formatted tables and charts
- Export Excel data to Access databases
- Create PowerPoint presentations with charts and data from Excel
To control another Office application, you use CreateObject or GetObject to get a reference to it, then work with its object model exactly the same way you work with Excel's. The concepts are identical — it's just a different set of objects and properties to learn.
Conclusion — You Now Know VBA
If you've worked through this guide in order, you've covered more VBA than most people learn in months of scattered tutorials. Let's be specific about what that means:
That's a complete foundation. You won't know everything — nobody does — but you know enough to automate real tasks, read code someone else has written, and learn what you don't know yet by building things.
What to Do Next
The worst thing you can do now is nothing. Learning VBA only sticks when you use it on real problems. Here are three concrete next steps: