The Ultimate Guide to VBA – Automate Anything in Excel

- Written by Puneet Gogia (Microsoft MVP)

80+ Excel Keyboard Shortcuts
Excel 365 Excel 2021 Excel 2019 Excel 2016 Mac — limited Excel Online — not supported

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:

Without VBA
Monthly sales report
Open 12 files, copy data into master sheet, clean formatting, build a pivot table, save, email.
25 minutes — every single month
With VBA
One macro does all of it. You click a button and walk away.
8 seconds
Without VBA
Cleaning messy data
Remove blanks, fix date formats, trim spaces, delete duplicates — row by row, column by column.
20 minutes of soul-destroying work
With VBA
A loop runs through every row and cleans everything in a single pass.
3 seconds
Without VBA
Weekly status emails
Copy numbers out of Excel, open Outlook, type the email, paste the data, send to 20 people. Every week.
10 minutes, every week, forever
With VBA
Macro reads the data, opens Outlook, writes and sends every email automatically.
1 click

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.

What this guide covers:
What VBA is and how it actually works Loops — automate repetitive actions VBA vs Python vs Office Scripts Worksheets, workbooks, and files Setting up Excel and the VBE Error handling — stop your code crashing Writing your first macro from scratch Using AI (ChatGPT + Copilot) with VBA Variables, data types, and scope VBA best practices and common mistakes

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:

Choose VBA if…
You use desktop Excel daily, you want to automate repetitive tasks right now, and you have no prior coding experience. Fastest route from zero to useful automation.
Choose Python if…
You work with large datasets (100k+ rows), need to connect Excel to databases or APIs, or want a programming skill that works outside of Office entirely.
Choose Office Scripts if…
Your organisation uses Excel Online exclusively, you need scripts other team members can run from the browser, and you already know JavaScript.
Choose Power Automate if…
You want to connect Excel with other apps — SharePoint, Teams, email, forms — without touching code. Great for file-level workflows, not cell-level Excel work.
My recommendation: Learn VBA first. It teaches you the fundamentals of programming logic — variables, loops, conditions — which makes learning Python or Office Scripts much easier later. And unlike those tools, VBA starts delivering value within your first week of learning it.

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:

1
Excel 2016 or newer — desktop version
VBA works in Excel 2016, 2019, 2021, and Microsoft 365 on both Windows and Mac. It does not work in Excel Online (the browser version) — you need the desktop app installed on your computer.
2
Basic Excel knowledge
You don't need to be an Excel expert. But you should know how to navigate cells and worksheets, write a basic formula like SUM or IF, and understand what a workbook is. That's enough to follow everything in this guide.
3
The Developer tab enabled
The Developer tab is hidden by default in Excel. You need to turn it on to access the VBA editor and run macros. We'll do this together in the next section — it takes about 30 seconds.
4
Save your files as .xlsm — not .xlsx
A standard Excel file (.xlsx) cannot store VBA code. Any file that contains macros must be saved as an Excel Macro-Enabled Workbook (.xlsm). Excel will remind you if you forget — just don't click "No" when it does.
5
No prior programming experience needed
Seriously. VBA was designed to be accessible to non-programmers. I've taught it to accountants, HR managers, and project coordinators who had never written a line of code in their lives. If you can follow a recipe, you can follow a VBA tutorial.
Excel for Mac users: VBA works on Mac, but with some important limitations. UserForms (pop-up dialog boxes), Windows API calls, and certain file system functions don't work on Mac. Everything in this guide will work on Mac unless I specifically note otherwise.
Important: Every time you create a new file to practice with in this guide, go to File ? Save As and choose Excel Macro-Enabled Workbook (.xlsm) as the file type. Do this before you write any code — not after.

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:

VBA syntax — object first, action second
Box.Open

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:

Methods vs Properties
Method An action you tell the object to perform. Like pressing a button. Example: Range("A1").Clear — clears the contents of cell A1.
Property A characteristic of the object you can read or change. Example: Range("A1").Value — gets or sets the value in cell A1.

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:

Example 1 — Range object (working with cells)
Sub RangeExamples() 'Write a value into cell A1 Range("A1").Value = "Hello, VBA!" 'Change the font colour of cell A1 to red Range("A1").Font.Color = RGB(255, 0, 0) 'Clear everything from a range of cells Range("A1:D10").Clear End Sub
Example 2 — Worksheet object (working with sheets)
Sub WorksheetExamples() 'Rename the first sheet Worksheets(1).Name = "Sales Data" 'Hide a sheet called "Working" Worksheets("Working").Visible = False 'Write a value into cell B2 on a specific sheet Worksheets("Sales Data").Range("B2").Value = "Q1 Total" End Sub
Example 3 — Workbook object (working with the file itself)
Sub WorkbookExamples() 'Save the current file ThisWorkbook.Save 'Get the name of the current file and show it MsgBox ThisWorkbook.Name 'Close the file without saving ThisWorkbook.Close SaveChanges:=False End Sub

Those three objects sit inside each other like Russian dolls — and that nesting is what makes VBA so logical once it clicks:

Workbook
The Excel file itself. Contains one or more worksheets.
ThisWorkbook.Save
Worksheet
An individual tab inside the workbook. Contains cells.
Worksheets("Sheet1")
Range
A cell or group of cells inside a worksheet. The most-used object in VBA.
Range("A1:D10")
The pattern to remember: In VBA, you always go from the biggest object to the smallest. Workbook → Worksheet → Range. And you connect them with dots. Once this feels natural — and it will, quickly — reading and writing VBA code becomes much easier.

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:

1
Click the File tab in the top-left corner of Excel.
2
Click Options at the bottom of the left sidebar. This opens the Excel Options dialog.
3
Click Customize Ribbon in the left panel of the Options dialog.
4
On the right side, you'll see a list of tabs. Find Developer and tick the checkbox next to it.
5
Click OK. The Developer tab will now appear in your Excel ribbon.
File > Options > Customize Ribbon > Developer > OK
1
Click the Excel menu in the top-left of your screen (in the Mac menu bar, not inside Excel).
2
Click Preferences to open the Excel Preferences dialog.
3
Click Ribbon & Toolbar.
4
Find Developer in the list of Main Tabs and tick the checkbox next to it.
5
Click Save. The Developer tab will now appear in your ribbon.
Excel menu ? Preferences ? Ribbon & Toolbar ? Developer ? Save
Quick shortcut: Once the Developer tab is enabled, press Alt + F11 on Windows (or Fn + Option + F11 on Mac) at any time to open the Visual Basic Editor directly. You'll use this shortcut constantly.

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:

Microsoft Visual Basic for Applications — [Book1.xlsm]
VBAProject (Book1.xlsm)
Microsoft Excel Objects
Sheet1
ThisWorkbook
Modules
Module1 «
Module1
Name    Module1
? Code window — you write here

Sub MyFirstMacro()
    'This is a comment
    Range("A1").Value = "Hello!"
End Sub
? Immediate window — type commands here to test instantly
? Range("A1").Value
Hello!
?
Project Window
Shows every file currently open in Excel and all their components — sheets, modules, and forms. Think of it as a file explorer for your VBA code.
?
Code Window
Where you write your macros. Double-click any module or sheet in the Project Window to open its code here. This is where you'll spend 95% of your time.
?
Properties Window
Shows the properties of whatever is selected in the Project Window. Useful for renaming modules and setting form properties, but you won't use it often when starting out.
?
Immediate Window
A scratchpad where you can type and run single lines of VBA instantly without writing a full macro. Brilliant for testing and debugging. Press Ctrl+G to open it if it's not visible.
Don't see the Immediate Window? Go to View ? Immediate Window in the VBE menu bar, or press Ctrl + G. I'd recommend keeping it open whenever you're coding — it saves a lot of time when testing individual lines.

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

1
Click on cell A1 in a blank worksheet.
2
Go to the Developer tab and click Record Macro. A dialog box will appear.
3
Give it a name — something like FormatCell — and click OK. Recording has started. Everything you do in Excel from this point is being captured.
4
Type Hello in cell A1 and press Enter. Then click back on A1, make it Bold (Ctrl+B), and change the fill colour to yellow using the Home tab.
5
Go back to the Developer tab and click Stop Recording.
6
Press Alt + F11 to open the VBE. In the Project Window, expand Modules and double-click Module1. You'll see the code Excel generated for you.

Your recorded macro will look something like this:

What Excel generated — your first VBA code
Sub FormatCell() ' ' FormatCell Macro ' Range("A1").Select ActiveCell.FormulaR1C1 = "Hello" Range("A1").Select With Selection.Font .Bold = True End With With Selection.Interior .Color = 65535 End With End Sub

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.

Common mistake — Absolute vs Relative recording: By default, the macro recorder uses absolute references — it records the exact cell addresses you clicked. So if you recorded clicking on A1, the macro will always run on A1, no matter what cell you're in when you run it. To record relative to your current position, click Use Relative References in the Developer tab before you start recording.

Two things the recorder teaches you that no tutorial mentions

The recorder is a syntax cheat sheet
Can't remember how to change a cell's background colour in VBA? Record yourself doing it manually. Excel writes the code for you. I still do this after 15 years when I need an obscure property.
Recorded code is verbose — clean it up
The recorder adds a lot of .Select and Selection. lines that aren't needed. As you get better at VBA, you'll learn to reference objects directly instead — it's faster and cleaner.
Try this: Run the macro you just recorded by pressing F5 inside the VBE, or go to Developer tab ? Macros ? select FormatCell ? Run. Then undo everything (Ctrl+Z), change the macro so the colour becomes green instead of yellow, and run it again. You just edited VBA code. That's all programming is.

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:

1
Press Alt + F11 to open the Visual Basic Editor.
2
In the Project Window, right-click your workbook name ? Insert ? Module. A blank code window opens.
3
Type your macro — starting with Sub and ending with End Sub. Everything between those two lines is your code.
4
Press F5 to run it, or place your cursor anywhere inside the Sub and press F5.

Let's write three macros — each one slightly more useful than the last.

Example 1 Enter a value into a cell

The simplest possible macro — write a value into a specific cell. This teaches you the core pattern: object ? property ? value.

Write "Done" into cell A1
Sub EnterValue() 'Write the word Done into cell A1 Range("A1").Value = "Done" 'Write a number into cell B1 Range("B1").Value = 1000 'Write today's date into cell C1 Range("C1").Value = Date End Sub
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.
Try it: Change "Done" to your own name. Run it. Change 1000 to any number. Run it again. This is how you learn — tweak, run, observe. Every time something doesn't work the way you expected, you've learned something.
Example 2 Loop through rows and highlight blank cells

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.

Highlight all blank cells in column A red
Sub HighlightBlanks() Dim i As Long Dim lastRow As Long 'Find the last row with data in column A lastRow = Cells(Rows.Count, 1).End(xlUp).Row 'Loop through every row from row 1 to the last row For i = 1 To lastRow 'If the cell in column A is empty, fill it red If Cells(i, 1).Value = "" Then Cells(i, 1).Interior.Color = RGB(255, 0, 0) End If Next i End Sub
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.
Notice the pattern: Even without knowing loops formally, you can read this and understand it. That's the goal of this example. When we cover loops properly in Stage 2, this will all make perfect sense.
Example 3 Save the file with today's date in the name

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.

Save the current file with today's date in the filename
Sub SaveWithDate() Dim fileName As String Dim savePath As String 'Build the filename using today's date 'Format() converts the date to a readable string like 2026-04-04 fileName = "Sales Report - " & Format(Date, "YYYY-MM-DD") & ".xlsm" 'Set the folder path where you want to save savePath = "C:\Users\Puneet\Documents\" 'Save the file with the full path + filename ThisWorkbook.SaveAs savePath & fileName 'Confirm it worked MsgBox "Saved as: " & fileName End Sub
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.
Common mistake — the savePath: Change C:\Users\Puneet\Documents\ to a real folder on your computer before running this. If the folder doesn't exist, VBA will throw a runtime error. Also make sure the path ends with a backslash \ — without it, VBA will try to save a file called "DocumentsSales Report..." which doesn't exist.
Where to go from here: You've now written three macros and can read VBA syntax. Everything that follows in this guide builds directly on these foundations — variables, conditions, loops, and error handling are just ways to make your macros smarter and more flexible. Let's start with variables.

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

Range("A1")
Uses the cell address as a string. Readable and familiar — looks exactly like an Excel formula reference.
Use when: the cell address is fixed and you know it upfront.
Cells(1, 1)
Uses row and column numbers instead of a letter address. Cells(1,1) = A1, Cells(2,3) = C2.
Use when: you're inside a loop and the row or column number changes dynamically.
ActiveCell
Refers to whichever cell is currently selected in Excel when the macro runs. Unpredictable.
Use when: you intentionally want the macro to act on wherever the user has clicked.
Example 1 Range vs Cells — same result, different approach

These three blocks of code do exactly the same thing — write "Hello" into cell C4. The syntax is what differs.

Three ways to write to the same cell
Sub ThreeWaysToReferToACell() 'Method 1 — Range with address string (most readable) Range("C4").Value = "Hello" 'Method 2 — Cells with row and column numbers 'Cells(4, 3) = row 4, column 3 = C4 Cells(4, 3).Value = "Hello" 'Method 3 — Range with a variable for the address Dim cellAddress As String cellAddress = "C4" Range(cellAddress).Value = "Hello" 'Writing to a range of cells in one line Range("A1:A10").Value = "Filled" End Sub
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.
Which should you use? Use Range("A1") when you know the address. Use Cells(row, col) inside loops where the row or column number is changing — you'll see why in Block 11 when we cover loops.
Example 2 Reading from and writing to cells

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.

Read values, calculate, write result
Sub CalculateTotal() Dim price As Double Dim quantity As Long Dim total As Double Dim discount As Double 'Read values from the sheet into variables price = Range("B2").Value quantity = Range("C2").Value discount = Range("D2").Value 'Calculate the discounted total total = (price * quantity) * (1 - discount) 'Write the result back to the sheet Range("E2").Value = total 'Format the result cell as currency Range("E2").NumberFormat = "$#,##0.00" End Sub
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.
Example 3 The LastRow pattern — the most important pattern in practical VBA

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.

Find the last row and work with the full data range
Sub LastRowPattern() Dim lastRow As Long Dim dataRange As Range 'Find the last row with data in column A 'This works by going to the very bottom of the sheet 'then pressing Ctrl+Up to find the last used cell lastRow = Cells(Rows.Count, 1).End(xlUp).Row 'Now use lastRow to define a dynamic range Set dataRange = Range("A1:A" & lastRow) 'Do something with the full range — bold it dataRange.Font.Bold = True 'Show how many rows were found MsgBox "Found " & lastRow & " rows of data." End Sub
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.
Common mistake — forgetting Set: When you assign a Range object to a variable, you must write Set dataRange = Range("A1") — not dataRange = Range("A1"). Omitting Set gives you a "Object required" runtime error. The rule is simple: if it's an object (Range, Worksheet, Workbook), use Set. If it's a value (number, text, date), don't.
Example 4 Offset — navigating relative to a cell

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.

Using Offset to navigate relative to a cell
Sub UsingOffset() 'Start at A1 Dim startCell As Range Set startCell = Range("A1") 'Offset(rows, columns) — move from startCell 'Move 1 row down, 0 columns across = A2 startCell.Offset(1, 0).Value = "One row below A1" 'Move 0 rows down, 1 column across = B1 startCell.Offset(0, 1).Value = "One column right of A1" 'Move 3 rows down, 2 columns across = C4 startCell.Offset(3, 2).Value = "Row 4, Column C" 'Practical use: read a value, write result to the next column Dim salesValue As Double salesValue = startCell.Value startCell.Offset(0, 1).Value = salesValue * 1.2 '20% uplift in column B End Sub
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.
Where Offset really shines: Inside a loop, when you're on row i and need to write a result into the column next to it — Cells(i, 1).Offset(0, 1).Value = result — rather than calculating the column reference manually. Once you start using loops in the next section, Offset will become one of your most-used tools.
The most common Range mistake — hardcoded addresses: Writing Range("A1:A100") and assuming your data always has exactly 100 rows is a trap. When your dataset grows to 150 rows, the macro silently ignores the last 50. Always use the LastRow pattern from Example 3 for any range that processes real data.

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.

Rule: always use ThisWorkbook, never ActiveWorkbook. I have spent hours debugging macros that worked perfectly in testing and then corrupted the wrong file in production — every single time, the cause was ActiveWorkbook switching unexpectedly when a second file was opened. Just use ThisWorkbook. It never lies to you.
Example 1 Common worksheet operations — add, rename, hide, delete

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.

Add, rename, hide, and safely delete a worksheet
Sub WorksheetOperations() Dim ws As Worksheet '--- ADD a new sheet at the end of all tabs --- Set ws = ThisWorkbook.Worksheets.Add( _ After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count)) '--- RENAME the new sheet --- ws.Name = "Monthly Summary" '--- HIDE a sheet (still exists, just not visible) --- ThisWorkbook.Worksheets("Working Data").Visible = False '--- UNHIDE a sheet --- ThisWorkbook.Worksheets("Working Data").Visible = True '--- DELETE a sheet safely (suppress the confirmation prompt) --- Application.DisplayAlerts = False ThisWorkbook.Worksheets("Old Data").Delete Application.DisplayAlerts = True '--- COPY a sheet to the end of the workbook --- ThisWorkbook.Worksheets("Template").Copy _ After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count) End Sub
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.
Always check a sheet exists before deleting it. If the sheet named "Old Data" doesn't exist, the Delete line throws a runtime error and stops your macro. Add a helper function that checks for the sheet's existence first — we'll cover that pattern properly in the Error Handling block.
Example 2 Workbook operations — open, read, copy, close

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.

Open an external file, copy data, close it
Sub ImportFromExternalFile() Dim sourceWB As Workbook Dim sourceWS As Worksheet Dim destWS As Worksheet Dim filePath As String 'Path to the source file — change this to your actual path filePath = "C:\Reports\SalesData.xlsx" 'Turn off screen updating so the user doesn't see files flashing open Application.ScreenUpdating = False 'Open the source workbook Set sourceWB = Workbooks.Open(filePath) Set sourceWS = sourceWB.Worksheets("Sheet1") 'Set the destination — the sheet in THIS workbook Set destWS = ThisWorkbook.Worksheets("Import") 'Copy the used range from source to destination sourceWS.UsedRange.Copy destWS.Range("A1").PasteSpecial xlPasteValues 'Close the source file without saving sourceWB.Close SaveChanges:=False 'Turn screen updating back on Application.ScreenUpdating = True MsgBox "Import complete." End Sub
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.
Common mistake — losing the workbook reference: After Workbooks.Open(filePath), the opened file becomes the ActiveWorkbook. If you then write ActiveWorkbook.Worksheets("Sheet1") it works — for now. But the moment any other code runs that changes the active window, it breaks silently. Always assign the opened workbook to a variable with Set and use that variable throughout.
Example 3 Export every sheet as a separate PDF

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.

Loop through all sheets and save each as a PDF
Sub ExportAllSheetsAsPDF() Dim ws As Worksheet Dim savePath As String Dim fileName As String Dim exported As Long 'Folder where PDFs will be saved — must already exist savePath = "C:\Reports\PDFs\" exported = 0 Application.ScreenUpdating = False For Each ws In ThisWorkbook.Worksheets 'Skip hidden sheets — they're hidden for a reason If ws.Visible = True Then 'Build the filename from the sheet name and today's date fileName = ws.Name & " - " & Format(Date, "YYYY-MM-DD") & ".pdf" 'Export as PDF ws.ExportAsFixedFormat _ Type:=xlTypePDF, _ Filename:=savePath & fileName, _ Quality:=xlQualityStandard exported = exported + 1 End If Next ws Application.ScreenUpdating = True MsgBox exported & " PDFs saved to " & savePath End Sub
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.
Real-world tip: Add a check that the save folder actually exists before the loop runs. If savePath doesn't exist, ExportAsFixedFormat throws a runtime error on the first sheet and stops everything. Use If Dir(savePath, vbDirectory) = "" Then MkDir savePath before the For Each loop to create the folder automatically if it's missing.

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:

Variable declaration syntax
Dim variableName As DataType

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 Variant trap: If you declare a variable without specifying a data type — Dim x instead of Dim x As Long — VBA automatically makes it a Variant. Variants use more memory, run slower, and can hide bugs because VBA won't complain if you accidentally put text into a variable that should hold a number. Always specify your data type.
Example 1 Basic variable declaration and use

The simplest way to see variables in action — store a few values and use them to write into cells.

Storing and using variable values
Sub BasicVariables() 'Declare variables with their types Dim employeeName As String Dim salary As Double Dim isActive As Boolean 'Assign values to each variable employeeName = "Priya Sharma" salary = 52000.50 isActive = True 'Write the values into cells Range("A1").Value = employeeName Range("B1").Value = salary Range("C1").Value = isActive End Sub
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.
Example 2 Using variables in a real calculation

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.

Calculate annual bonus from cell values
Sub CalculateBonus() Dim baseSalary As Double Dim bonusRate As Double Dim bonusAmount As Double 'Read values from cells instead of hardcoding them baseSalary = Range("B2").Value bonusRate = Range("C2").Value 'Calculate the bonus bonusAmount = baseSalary * bonusRate 'Write the result back to the sheet Range("D2").Value = bonusAmount 'Show a confirmation message MsgBox "Bonus calculated: " & bonusAmount End Sub
Try it: Put a salary in B2 and a rate (like 0.1 for 10%) in C2, then run this macro. Now change the bonus rate and run it again — you only change one cell, and the whole calculation updates. This is the real power of reading from cells rather than hardcoding values in your code.
Example 3 Variable scope — local vs module vs global

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.

Local
Declared inside a Sub with Dim. Only visible within that Sub. Dies when the Sub finishes. Dim x As Long
Module-level
Declared at the top of a module, outside any Sub. Visible to all Subs in that module. Dim x As Long (top of module)
Global
Declared at the top of any module using Public. Visible to every Sub in every module. Public x As Long
Scope in action — two Subs sharing a module-level variable
'Declared at the top of the module — visible to all Subs below Dim reportTitle As String Sub SetTitle() 'Set the module-level variable reportTitle = "Q1 Sales Summary" End Sub Sub UseTitle() 'This Sub can read reportTitle even though it was set in SetTitle() Range("A1").Value = reportTitle MsgBox "Report title is: " & reportTitle End Sub
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:

Put this at the top of every module
Option Explicit Sub MyMacro() 'Your code here... End Sub
Why Option Explicit saves you hours of debugging: Without it, if you accidentally type sallary instead of salary, VBA creates a brand new variable called sallary with an empty value — and your code runs without complaining, silently producing wrong results. With Option Explicit, VBA stops immediately and tells you "Variable not declared" — pointing you straight to the typo. I've never written a module without it in 15 years.
Make it automatic: In the VBE, go to Tools ? Options ? Editor tab and tick Require Variable Declaration. VBA will automatically add Option Explicit to the top of every new module you create from now on.

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

Anatomy of an IF statement
If condition Then The condition is anything that evaluates to True or False. If it's True, VBA runs the code below this line.
'code if True The code that runs when the condition is met. Can be as many lines as you need.
ElseIf condition Then Optional. Check a second condition if the first one was False. You can have as many ElseIf blocks as you need.
Else Optional. Runs if every condition above it was False — the catch-all fallback.
End If Always required. Closes the IF block. Every If needs an End If.

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
Example 1 IF Then Else — check a value and take action

A practical macro that checks a sales figure and labels it as either above or below target — then colours the cell to match.

Flag sales performance against a target
Sub CheckSalesTarget() Dim salesAmount As Double Dim target As Double salesAmount = Range("B2").Value target = Range("C2").Value If salesAmount >= target Then 'Above or on target — green with "Met" label Range("D2").Value = "Met" Range("D2").Interior.Color = RGB(198, 239, 206) ElseIf salesAmount >= target * 0.8 Then 'Within 80% of target — amber warning Range("D2").Value = "Close" Range("D2").Interior.Color = RGB(255, 235, 156) Else 'Below 80% of target — red flag Range("D2").Value = "Missed" Range("D2").Interior.Color = RGB(255, 199, 206) End If End Sub
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.
Try it: Put a sales figure in B2 and a target in C2. Run the macro. Then change B2 to different values and run it again to see each outcome. This is exactly the kind of macro that can replace a column of IF formulas that would break if someone deleted a row.
Example 2 Multiple conditions with And / Or

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.

Grade assignment with multiple conditions
Sub AssignGrade() Dim score As Long Dim attendance As Double Dim grade As String score = Range("B2").Value attendance = Range("C2").Value 'as a percentage e.g. 0.85 = 85% 'Must have 75%+ attendance AND score 90+ for an A If score >= 90 And attendance >= 0.75 Then grade = "A" ElseIf score >= 75 And attendance >= 0.75 Then grade = "B" ElseIf score >= 60 Then grade = "C" 'Failed score OR failed attendance ElseIf score < 60 Or attendance < 0.5 Then grade = "F" Else grade = "Incomplete" End If Range("D2").Value = grade End Sub
Common mistake — nested IF hell: When you find yourself writing an IF inside an IF inside another IF, stop. More than two levels of nesting becomes almost impossible to read and debug. That's usually the signal to either use ElseIf (which keeps everything flat) or switch to Select Case. I've seen macros with 6 levels of nested IFs that nobody — including the person who wrote them — could follow three months later.
Example 3 Select Case — cleaner for multiple fixed values

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.

With IF — gets messy fast
If region = "North" Then
  code = "N"
ElseIf region = "South" Then
  code = "S"
ElseIf region = "East" Then
  code = "E"
ElseIf region = "West" Then
  code = "W"
End If
With Select Case — much cleaner
Select Case region
  Case "North": code = "N"
  Case "South": code = "S"
  Case "East":  code = "E"
  Case "West":  code = "W"
  Case Else:    code = "?"
End Select
Select Case with ranges of values — bonus tier calculator
Sub BonusTier() Dim salesRank As Long Dim bonusPercent As Double salesRank = Range("B2").Value Select Case salesRank Case 1 'Exactly rank 1 bonusPercent = 0.2 Case 2 To 5 'Ranks 2 through 5 bonusPercent = 0.15 Case 6 To 10 'Ranks 6 through 10 bonusPercent = 0.1 Case Is > 10 'Anything above 10 bonusPercent = 0.05 Case Else 'Unrecognised value — handle gracefully MsgBox "Invalid rank in B2" Exit Sub End Select Range("C2").Value = bonusPercent Range("C2").NumberFormat = "0%" End Sub
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.
When to use Select Case vs IF: Use IF when your conditions involve different variables or complex logical expressions (And, Or). Use Select Case when you're testing the same single variable against multiple possible values — especially when those values are fixed options or ranges of numbers. Select Case is easier to extend, easier to read, and easier to hand off to someone else.

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:

For Next
Runs a fixed number of times. You control the start, end, and step size.
Use when: you know exactly how many times to loop — e.g. rows 1 to 100.
For Each
Loops through every item in a collection — every cell in a range, every sheet in a workbook.
Use when: you want to process every object in a group without counting them.
Do While
Keeps running as long as a condition is True. Stops the moment it becomes False.
Use when: you don't know how many iterations you need — it depends on the data.
Example 1 For Next — loop through rows and apply logic

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.

Colour-code every row based on sales performance
Sub ColourCodeRows() Dim i As Long Dim lastRow As Long Dim sales As Double Dim target As Double 'Find the last row of data dynamically (LastRow pattern) lastRow = Cells(Rows.Count, 1).End(xlUp).Row 'Loop from row 2 (skip the header) to the last row For i = 2 To lastRow 'Read sales and target for this row sales = Cells(i, 2).Value 'Column B target = Cells(i, 3).Value 'Column C 'Apply colour based on performance If sales >= target Then Cells(i, 2).Interior.Color = RGB(198, 239, 206) 'Green Else Cells(i, 2).Interior.Color = RGB(255, 199, 206) 'Red End If Next i MsgBox "Done — " & (lastRow - 1) & " rows processed." End Sub
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.
Step keyword: By default, For Next increases the counter by 1 each time. You can change this with Step: For i = 1 To 100 Step 2 counts 1, 3, 5, 7... and For i = 10 To 1 Step -1 counts backwards from 10 to 1. Counting backwards is essential when deleting rows inside a loop — always delete from the bottom up, or the row numbers shift and you skip rows.
Example 2 For Each — loop through every worksheet

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.

Add a formatted header to every sheet in the workbook
Sub FormatAllSheets() Dim ws As Worksheet 'Loop through every worksheet in the workbook For Each ws In ThisWorkbook.Worksheets 'Write the sheet name into cell A1 as a header ws.Range("A1").Value = ws.Name & " — Report" 'Format it: bold, larger font, green background ws.Range("A1").Font.Bold = True ws.Range("A1").Interior.Color = RGB(198, 239, 206) 'Skip the sheet called "Summary" — it has a different layout If ws.Name = "Summary" Then ws.Range("A1").Interior.Color = RGB(189, 215, 238) 'Blue for Summary End If Next ws MsgBox "All " & ThisWorkbook.Worksheets.Count & " sheets formatted." End Sub
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.
For Each works on ranges too: For Each cell In Range("A1:A100") loops through every cell in that range, setting cell to each one in turn. This is useful when you don't need the row number — you just want to do something to every cell. When you do need the row number (to write to an adjacent column), use For Next with Cells(i, col) instead.
Example 3 Do While — loop until the data runs out

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.

Process rows until an empty cell is found
Sub ProcessUntilEmpty() Dim currentRow As Long Dim cellValue As String currentRow = 2 'Start at row 2 (skip header) 'Keep looping while column A is not empty Do While Cells(currentRow, 1).Value <> "" cellValue = Cells(currentRow, 1).Value 'Write the value uppercased into column B Cells(currentRow, 2).Value = UCase(cellValue) 'Move to the next row currentRow = currentRow + 1 Loop MsgBox "Processed " & (currentRow - 2) & " rows." End Sub
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.
The infinite loop trap — the most dangerous VBA mistake: If your Do While condition never becomes False, the loop runs forever and freezes Excel. The most common cause is forgetting to increment the counter (currentRow = currentRow + 1) or writing a condition that can never be False. If this happens, press Escape or Ctrl + Break to stop the code. Always double-check your exit condition before running a Do While loop on real data.
Example 4 Exit For — escape a loop early when you've found what you need

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.

Find the first row where stock has run out
Sub FindFirstOutOfStock() Dim i As Long Dim lastRow As Long Dim foundRow As Long lastRow = Cells(Rows.Count, 1).End(xlUp).Row foundRow = 0 '0 means not found yet For i = 2 To lastRow 'Check if stock quantity in column C is zero If Cells(i, 3).Value = 0 Then foundRow = i Exit For 'Stop the loop — we found what we needed End If Next i 'Report the result If foundRow > 0 Then Cells(foundRow, 3).Interior.Color = RGB(255, 199, 206) MsgBox "First out-of-stock item found on row " & foundRow Else MsgBox "All items are in stock." End If End Sub
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.
Exit Do works the same way for Do While loops. Use Exit For inside a For loop, Exit Do inside a Do While loop. Both immediately stop the loop and move to the next line of code after the loop block. These are not errors or shortcuts — they're the correct, intentional way to stop a loop early and you'll use them constantly in real macros.
A performance tip worth knowing now: Loops that interact with the spreadsheet cell by cell are much slower than loops that work with variables. If you're looping through thousands of rows, turn off screen updating at the start of your macro with Application.ScreenUpdating = False and turn it back on at the end with Application.ScreenUpdating = True. This alone can make your macros run 5–10x faster on large datasets.

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

Syntax errors
Typos and grammatical mistakes in your code. VBA catches these as you type and highlights the line in red. Fix them before running. If x = 1 ? missing Then
Compile errors
Logical structure problems VBA finds when it compiles your code before running — like a Sub with no End Sub, or a variable used before it's declared. Sub MyMacro() ? no End Sub
Runtime errors
Errors that only appear when the code runs — a file that doesn't exist, a sheet that was renamed, dividing by zero. These are what error handling is designed to catch. Error 9: Subscript out of range
Logical errors
The worst kind — code that runs without errors but produces the wrong result. No handler catches these. Only careful testing and code review finds them. total = price + tax ? should be *

The On Error statement — three modes

Three ways to handle errors in VBA
On Error GoTo Label When an error occurs, jump to a labelled section of code that handles it. The standard approach for production macros — use this for almost everything.
On Error Resume Next When an error occurs, ignore it and continue to the next line. Dangerous if overused — it hides errors you actually need to know about. Use it only for a single specific operation, then reset immediately.
On Error GoTo 0 Resets error handling back to VBA's default — errors will crash the macro again. Always call this after using On Error Resume Next to re-enable normal error catching.
Example 1 Basic On Error GoTo handler

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.

On Error GoTo — the standard handler pattern
Sub OpenFileWithErrorHandling() Dim wb As Workbook Dim filePath As String 'Set the error handler — must be before any code that might fail On Error GoTo ErrorHandler filePath = "C:\Reports\Sales.xlsx" 'This line will fail if the file doesn't exist Set wb = Workbooks.Open(filePath) 'Do your work here... wb.Worksheets("Data").Range("A1").Value = "Imported" wb.Close SaveChanges:=False 'Exit before reaching the error handler Exit Sub '--- Error handler section --- ErrorHandler: MsgBox "Something went wrong:" & vbNewLine & _ "Error " & Err.Number & ": " & Err.Description, _ vbCritical, "Macro Error" 'Clean up — close the file if it was opened before the error If Not wb Is Nothing Then wb.Close SaveChanges:=False End Sub
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.
Example 2 On Error Resume Next — the right way to use it

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.

Check if a sheet exists without crashing
Sub CheckSheetExists() Dim ws As Worksheet Dim sheetName As String sheetName = "January" 'Attempt to set ws — if the sheet doesn't exist, ws stays Nothing On Error Resume Next Set ws = ThisWorkbook.Worksheets(sheetName) On Error GoTo 0 'Reset immediately — do not leave Resume Next active 'Now check if ws was successfully set If ws Is Nothing Then MsgBox "Sheet '" & sheetName & "' does not exist." Else MsgBox "Sheet '" & sheetName & "' found — row count: " & ws.UsedRange.Rows.Count End If End Sub
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.
The On Error Resume Next trap — the most abused VBA statement: Wrapping an entire Sub in On Error Resume Next means errors are silently ignored everywhere. Your macro appears to run fine, but produces wrong results with no indication of what went wrong. I've inherited macros like this that had been silently failing for months. The rule is simple: On Error Resume Next should never span more than two or three lines. Always reset with On Error GoTo 0 immediately after the specific operation you needed it for.
Example 3 Production-ready error handler template

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.

Production error handler — copy this into every real macro
Sub ProductionMacroTemplate() '--- Declare all variables at the top --- Dim ws As Worksheet Dim lastRow As Long '--- Set error handler before anything else --- On Error GoTo ErrorHandler '--- Turn off settings for performance --- Application.ScreenUpdating = False Application.Calculation = xlCalculationManual Application.DisplayAlerts = False '--- Your actual code goes here --- Set ws = ThisWorkbook.Worksheets("Sales") lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row '... rest of your work ... '--- Cleanup on success --- CleanUp: Application.ScreenUpdating = True Application.Calculation = xlCalculationAutomatic Application.DisplayAlerts = True Exit Sub '--- Error handler --- ErrorHandler: MsgBox "Macro failed on line " & Erl & vbNewLine & _ "Error " & Err.Number & ": " & Err.Description, _ vbCritical, "Error" Resume CleanUp 'Jump to CleanUp to restore settings before exiting End Sub
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.
The most important habit in this entire guide: Always restore Application.ScreenUpdating, Application.Calculation, and Application.DisplayAlerts in your error handler — not just at the end of successful runs. If your macro crashes with ScreenUpdating still False, Excel becomes partially frozen and the user thinks something has gone badly wrong. The CleanUp label pattern above is the cleanest way to guarantee these always get reset, no matter what happens.
s 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 scoresClears the array and frees the memory it used. For large arrays on tight memory systems, always Erase when you're done.
Common mistake — ReDim wipes your data: If you ReDim an array that already has values in it, those values are destroyed. Use ReDim Preserve scores(1 To newSize) to resize while keeping existing values. Note that ReDim Preserve can only change the last dimension of a multi-dimensional array — it can't change the first.

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

Static array
Size is fixed when you declare it. Use when you know exactly how many items you'll store. Dim scores(1 To 10) As Long Holds 10 Long values, indexed 1 through 10.
Dynamic array
Size is set at runtime with ReDim. Use when you don't know the size upfront — which is most of the time. Dim scores() As Long
ReDim scores(1 To lastRow)
Size determined after you know lastRow.
Example 1 Working with arrays — declare, fill, loop, read

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.

Declare, populate, and loop through an array
Sub ArrayBasics() '--- Static array: size fixed at declaration --- Dim months(1 To 12) As String 'Assign values by index months(1) = "January" months(2) = "February" months(3) = "March" '... and so on months(12) = "December" 'Loop using LBound and UBound — always use these instead of hardcoding Dim i As Long For i = LBound(months) To UBound(months) Cells(i, 1).Value = months(i) Next i '--- Dynamic array: size set at runtime --- Dim scores() As Double Dim count As Long count = 5 'In reality this would come from your data ReDim scores(1 To count) 'Fill with values For i = 1 To count scores(i) = i * 10.5 Next i 'Read a specific element MsgBox "Third score: " & scores(3) 'Clear the array when done Erase scores End Sub
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.
Common mistake — ReDim wipes your data: If you ReDim an array that already has values in it, those values are destroyed. Use ReDim Preserve scores(1 To newSize) to resize while keeping existing values. Note that ReDim Preserve can only change the last dimension of a multi-dimensional array — it can't change the first.
Example 2 The range-to-array pattern — process 50,000 rows in under a second

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.

Why this is so much faster
Cell-by-cell loop on 50,000 rows: ~45 seconds. Same operation using range-to-array: ~0.3 seconds. Every time your loop touches a cell — Cells(i, 1).Value — VBA crosses the boundary between the VBA engine and the Excel object model. Arrays keep everything inside the VBA engine where there's no boundary to cross.
Load range ? process in memory ? write back in one shot
Sub RangeToArrayPattern() Dim dataArr() As Variant Dim resultArr() As Variant Dim ws As Worksheet Dim lastRow As Long Dim i As Long Set ws = ThisWorkbook.Worksheets("Sales") lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row Application.ScreenUpdating = False Application.Calculation = xlCalculationManual 'STEP 1 — Load the entire range into an array in ONE read 'This is one single interaction with Excel, not 50,000 dataArr = ws.Range("A2:B" & lastRow).Value 'STEP 2 — Prepare the result array (same number of rows, 1 column) ReDim resultArr(1 To lastRow - 1, 1 To 1) 'STEP 3 — Process entirely in memory — no cell reads inside this loop For i = 1 To UBound(dataArr, 1) 'dataArr(row, column) — col 1 = col A, col 2 = col B If dataArr(i, 2) >= 10000 Then resultArr(i, 1) = dataArr(i, 1) * 1.1 '10% uplift Else resultArr(i, 1) = dataArr(i, 1) 'No change End If Next i 'STEP 4 — Write all results back to the sheet in ONE write ws.Range("C2:C" & lastRow).Value = resultArr Application.ScreenUpdating = True Application.Calculation = xlCalculationAutomatic MsgBox "Done — " & (lastRow - 1) & " rows processed." End Sub
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.
When to use this pattern: Any time your loop processes more than a few hundred rows. The crossover point where the array approach becomes noticeably faster is around 500–1,000 rows. Below that, the difference is imperceptible and the simpler cell-by-cell loop is fine. Above 5,000 rows, the array pattern is essentially mandatory if you care about performance.
The Variant type for 2D arrays: When loading a range with .Value, always declare the array as Variant — not String, Long, or Double. The range might contain mixed types (numbers, text, dates, errors) and Variant is the only type that can hold all of them. Inside the loop, you can then check or convert the individual values as needed.

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

ChatGPT (GPT-4 / GPT-4o)
Excellent at generating VBA from detailed descriptions
Great at explaining what existing code does line by line
Can debug code you paste in — tell it the error message
Works across any browser or device — not tied to Excel
Microsoft Copilot in Excel
Directly integrated into Excel — generates and inserts macros
Can reference your actual data headers and sheet names
Useful for simple automation tasks on the current file
Requires Microsoft 365 Business subscription

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.

Weak prompt
Write a VBA macro to copy data
Too vague. AI doesn't know which sheet, which columns, where to copy to, or what conditions apply. The code it produces will be generic and probably wrong for your use case.
Strong prompt
Write an Excel VBA macro that loops through all rows in the "Sales" sheet starting at row 2. If the value in column C is greater than 10000, copy the entire row to a sheet called "High Value". Use the LastRow pattern to find the last row dynamically. Include error handling using On Error GoTo. Add comments to every block of code.
Sheet names, column references, condition, destination, patterns to use, and output style all specified. AI will produce something very close to production-ready.

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:

1
Read it before running it. Never paste AI code and hit F5 immediately. Read every line. If you've followed this guide, you should be able to understand 80-90% of what any AI-generated macro is doing. Anything you don't recognise — ask the AI to explain it.
2
Test on a copy of your data first. Make a backup copy of your workbook before running any AI-generated code on it. AI code can delete rows, overwrite data, or close files in ways you didn't expect.
3
When it throws an error, paste the error into the AI. Tell it: "This code gave me Runtime Error 9 Subscript out of range on the line Worksheets("Sales")." The AI will usually diagnose and fix it correctly — much faster than googling.
4
Check the sheet and column references. AI generates code based on what you described — not what your file actually looks like. The most common issue is the AI assuming a sheet is called "Sheet1" when yours is called "Data" or referencing column B when yours is column D.
5
Ask it to add Option Explicit and error handling if it didn't. AI often generates quick-and-dirty code without these. Ask explicitly: "Add Option Explicit at the top and a proper On Error GoTo error handler."

What AI cannot do for you in VBA

Honest limitations — know these before you rely on AI
AI doesn't know your file. It doesn't know your sheet names, your column headers, your data structure, or your business rules. You have to describe all of that in the prompt — and verify all of it in the generated code.
AI generates plausible-looking code, not necessarily correct code. It will confidently use methods that don't exist or properties with the wrong name. Reading the code before running it is the only way to catch this.
AI doesn't know your performance constraints. It will often generate cell-by-cell loops that work but are slow. If you're working with large datasets, explicitly ask for "the range-to-array pattern for performance" in your prompt.
You still need to understand the code to maintain it. A macro written entirely by AI that nobody on your team understands becomes a liability the moment something breaks or your data structure changes. This is why learning VBA properly — not just prompting AI — still matters.
My honest take after using AI for VBA daily: AI has made me faster — probably 30-40% faster on first drafts of complex macros. But it has not replaced the need to understand VBA. The people who get the most out of AI tools are the ones who understand the language well enough to evaluate, correct, and extend what the AI produces. The people who get burned by AI tools are the ones who run code they don't understand on data they can't afford to lose. Do the former, not the latter.
One more practical tip: Keep a conversation going rather than sending one prompt. Start with a basic version, run it, then tell the AI what went wrong or what you want to add. Iterating in conversation produces far better results than trying to write the perfect prompt on the first attempt.

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.

Hard limits — VBA cannot do these
Run in Excel Online. VBA requires the desktop Excel application. The browser version has no VBA engine — use Office Scripts instead.
Run on mobile. Excel for iOS and Android do not support VBA. Macros in a file opened on mobile are silently ignored.
Multi-thread. VBA runs on a single thread. It cannot parallelise work across CPU cores — long macros run sequentially, no exceptions.
Access real-time web data natively. VBA has no built-in HTTP client. Pulling live data from APIs requires using Windows' XMLHttpRequest object — possible but complex and Windows-only.
Mac limitations — works, but with caveats
UserForms are not supported on Mac. Pop-up dialog forms built with the UserForms editor in the VBE will not open on Mac.
Windows API calls fail silently. Any code that uses Declare to call Windows DLL functions (Sleep, Shell, file dialogs via API) will not work on Mac.
File paths use forward slashes. Mac uses / where Windows uses \. Hardcoded Windows paths in your macros will break on Mac — always build paths dynamically or use Application.PathSeparator.
Shell command behaves differently. The VBA Shell function works differently on Mac — it runs Unix shell commands, not Windows commands.
The right tool for the job: VBA is unbeatable for desktop Excel automation on Windows. For cloud-based work, use Office Scripts. For large-scale data processing or API integration, use Python. For cross-app workflows, use Power Automate. None of these replace each other — knowing when to reach for each one is the mark of an experienced Excel developer.

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.

1
Always use Option Explicit
Add it at the top of every module. Forces you to declare all variables before using them. Also catches typos that would otherwise create silent bugs. If you only adopt one habit from this list, make it this one.
Option Explicit
2
Name your variables clearly — not x, y, i2, temp
Use names that describe what the variable holds: lastRow not r, employeeName not n, salesTotal not s. The only acceptable short names are loop counters like i, j, k — because their purpose is obvious from context. You'll thank yourself six months later when you open the file and have no idea what you were thinking.
3
Turn off ScreenUpdating and Calculation in loops
At the start of any macro that touches many cells, add these two lines — and restore them at the end (including in the error handler). The performance difference on real datasets is significant.
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
4
Comment your code — not every line, but every block
You don't need a comment explaining what Range("A1").Value = "Done" does — it's obvious. But every logical block of code should have a one-line comment explaining why it's there, not what it does. "Loop backwards so row deletion doesn't skip rows" is a comment worth writing. "Loop through rows" is not.
5
Use error handlers in every macro that touches real data
Any macro that opens files, deletes rows, or writes data must have an On Error GoTo handler. Code that works on your test data will encounter unexpected input on someone else's file. An error handler is the difference between a macro that crashes and corrupts work, and one that stops gracefully and tells the user what happened.
6
Break long macros into smaller, named Subs
A Sub longer than 50-60 lines is a warning sign. Split it into smaller, well-named Subs and call them from a master Sub. ImportData, CleanData, FormatReport, ExportPDF is far easier to read and debug than one 200-line monster Sub that does all four things. Each Sub does one thing and does it well.

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.

Before — common beginner style
Sub Macro1() Dim x As Long Dim y As Long Dim z As Double y = Cells(Rows.Count,1).End(xlUp).Row For x = 2 To y z = Cells(x,2).Value If z > 10000 Then Cells(x,3).Value = z * 0.1 Else Cells(x,3).Value = z * 0.05 End If Next x End Sub
After — applying best practices
Option Explicit Sub CalculateSalesBonus() Dim i As Long Dim lastRow As Long Dim salesValue As Double On Error GoTo ErrorHandler Application.ScreenUpdating = False 'Find last row of sales data in column A lastRow = Cells(Rows.Count, 1).End(xlUp).Row 'Calculate bonus tier for each row For i = 2 To lastRow salesValue = Cells(i, 2).Value If salesValue > 10000 Then Cells(i, 3).Value = salesValue * 0.1 Else Cells(i, 3).Value = salesValue * 0.05 End If Next i Application.ScreenUpdating = True Exit Sub ErrorHandler: Application.ScreenUpdating = True MsgBox "Error: " & Err.Description, vbCritical End Sub

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.

The single best investment you can make in your VBA skills: Go back to macros you wrote six months ago and try to read them. If you can't follow them immediately, they needed more comments and better variable names. Use that discomfort as the benchmark — write code that your future self can read and maintain without needing to remember what you were thinking when you wrote it.
Indentation matters more than you think. VBA doesn't enforce indentation — the code runs the same whether it's indented or not. But indented code is dramatically easier to read and debug, especially when you're nesting loops inside conditions inside error handlers. Press Tab to indent inside every Sub, For, If, and With block. Press Shift+Tab to dedent when you close them. Make it a reflex.

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:

Introduction
Why VBA, when to use it vs alternatives, what you need to get started
Beginner
Set up Excel, recorded your first macro, wrote three from scratch — entering values, highlighting blanks, saving with a dated filename
Core skills
Variables, Ranges, Conditions, Loops, Worksheets/Workbooks, Error Handling — the six building blocks that make up 90% of all real-world VBA
Intermediate
UDFs, Arrays, AI-assisted coding, VBA limitations, Best practices — the skills that take you from "it works" to "it's reliable and maintainable"
Wrap up
7 common questions answered, honest limitations documented, best practices embedded

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:

1
Pick one thing you do manually every week and automate it
It doesn't have to be complex. A macro that formats a report, cleans a data file, or saves with today's date. One real automation cements more learning than ten exercises.
2
Explore the 100+ macro code examples on ExcelChamps
Now that you understand the fundamentals, reading real macro code is the fastest way to expand your skills. The macro codes page has 100+ examples organised by category — each one is a new pattern to add to your toolkit.
3
When you get stuck — use the resources around you
The VBE's built-in Help (F1 on any keyword), the macro recorder for syntax you can't remember, AI tools for first drafts, and the ExcelChamps tutorials for specific topics. You're not learning alone.
"The people who get the most out of VBA are not the ones who know the most syntax. They're the ones who look at a repetitive task and immediately ask — can I automate this? That question, asked consistently, is worth more than any individual piece of knowledge."
— Puneet Gogia, Microsoft MVP
Bookmark this page. Come back to it when you need a refresher on a specific concept — that's what it's here for. And if you found it useful, the best thing you can do is share it with a colleague who's been meaning to learn VBA but hasn't started yet. Getting someone else past that first hurdle is more satisfying than you'd think.