Excel TVExcelTV

Excel VBA Find Last Row: Efficient Data Range Detection

Excel VBA Find Last Row

Use lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row to find the last used row in a specific worksheet column. Qualify both Cells and Rows.Count with the worksheet, start from the bottom of the column, and move up to the first non-empty cell so the code works from any active sheet.

Sub FindLastRowInColumnA()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    MsgBox "Last row in column A: " & lastRow
End Sub

Key Takeaways

  • ws.Cells(ws.Rows.Count, "A").End(xlUp).Row is the standard pattern when one key column defines the data extent
  • Always qualify Cells, Rows, Range, and Columns with the worksheet you intend to inspect
  • Empty columns, formatting-only cells, and data spread across multiple columns need different handling

Find the Last Row in One Column

Most macros should start with a worksheet variable, then use that variable everywhere the range is referenced. That keeps the last-row calculation tied to the intended sheet instead of whatever sheet happens to be active when the macro runs.

Here is the pattern I use when column A is required for every record:

Dim ws As Worksheet
Dim lastRow As Long

Set ws = ThisWorkbook.Worksheets("Data")
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

The code starts at the bottom of column A on ws, then works upward exactly like pressing Ctrl + Up Arrow from the bottom of the sheet. If A1048576 is empty, it stops at the first non-empty cell above it and returns that row number.

The worksheet qualification matters. Cells(Rows.Count, "A") works only if the active sheet is the sheet you intended. In a macro that copies between sheets, imports files, or loops through worksheets, unqualified references are a common source of wrong-row bugs.

Use the Last Row in a Dynamic Range

Once lastRow is known, use it to build a range that grows and shrinks with the data. This is the practical reason most VBA macros find the last row in the first place.

Sub FormatDataRows()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    If lastRow < 2 Then Exit Sub

    ws.Range("A2:D" & lastRow).Font.Bold = False
End Sub

That If lastRow < 2 Then Exit Sub guard is useful when row 1 is a header and there may be no data rows. It prevents the macro from treating the header as a record.

You can use the same value in a loop:

Sub MarkHighValues()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim rowNum As Long

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    For rowNum = 2 To lastRow
        If ws.Cells(rowNum, "B").Value > 100 Then
            ws.Cells(rowNum, "C").Value = "High"
        End If
    Next rowNum
End Sub

This article stays focused on the last-row calculation. If you need the loop structure itself, see the separate guide to Excel VBA For loops. If your next step is moving the range to another sheet, see Excel VBA copy range.

Handle an Empty Column

The .End(xlUp) pattern has one important edge case: a completely empty column returns row 1. That does not mean row 1 contains data. It means Excel started at the bottom, found no non-empty cell above, and landed at the top of the sheet.

When column A may be empty, test the returned cell before using the result as a real data row:

Sub FindLastRowOrZero()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    If lastRow = 1 And Len(ws.Cells(1, "A").Value) = 0 Then
        lastRow = 0
    End If

    MsgBox "Data rows in column A end at row: " & lastRow
End Sub

I use 0 when I want the rest of the macro to clearly understand that no data row exists. If row 1 is a header and row 2 onward may be empty, I usually keep the header separate from the data check:

Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

If lastRow < 2 Then
    MsgBox "No data rows found."
    Exit Sub
End If

Choose the Right Column

The best last-row column is the column that must be filled for every valid record. In a sales export, that might be the transaction ID. In a customer list, it might be the customer name or account number. Avoid using a notes column, optional amount column, or formula column that may contain blanks.

Blank rows in the middle do not break .End(xlUp) as long as there is data below them in the same column. The method searches from the bottom up, not from the top down. A blank key column at the bottom, however, will cause the macro to stop at the last non-empty key cell, even if another column has data lower down.

If the dataset can end in different columns, use Find across the worksheet or scan the columns you care about.

Find the Last Used Row Across a Worksheet

When I need the last used row anywhere on a worksheet, I use Range.Find with SearchOrder:=xlByRows and SearchDirection:=xlPrevious. Unlike checking only column A, this searches the sheet for the last cell with content by row.

Sub FindLastUsedRowOnSheet()
    Dim ws As Worksheet
    Dim lastCell As Range

    Set ws = ThisWorkbook.Worksheets("Data")
    Set lastCell = ws.Cells.Find(What:="*", _
                                 After:=ws.Cells(1, 1), _
                                 LookIn:=xlFormulas, _
                                 LookAt:=xlPart, _
                                 SearchOrder:=xlByRows, _
                                 SearchDirection:=xlPrevious, _
                                 MatchCase:=False)

    If lastCell Is Nothing Then
        MsgBox "No used cells found."
    Else
        MsgBox "Last used row: " & lastCell.Row
    End If
End Sub

The If lastCell Is Nothing check is not optional on an empty worksheet. Without it, .Row raises an error because Find did not return a range.

LookIn:=xlFormulas includes cells containing formulas, even when a formula currently displays an empty string. If you only care about displayed values, use LookIn:=xlValues instead.

Find the Last Row in Specific Columns

Sometimes I want to ignore helper columns, notes, or old formulas off to the right. In that case, I check only the columns that define the dataset and keep the maximum row number.

Sub FindLastRowInSelectedColumns()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim col As Variant
    Dim candidateRow As Long

    Set ws = ThisWorkbook.Worksheets("Data")

    For Each col In Array("A", "B", "D")
        candidateRow = ws.Cells(ws.Rows.Count, col).End(xlUp).Row

        If candidateRow > 1 Or Len(ws.Cells(1, col).Value) > 0 Then
            If candidateRow > lastRow Then lastRow = candidateRow
        End If
    Next col

    MsgBox "Last row in selected columns: " & lastRow
End Sub

This is more deliberate than using every column on the sheet. It also avoids a common problem with UsedRange: a formatted cell or a previously used cell far below the actual data can make the used range look larger than the current dataset.

Methods to Avoid or Use Carefully

UsedRange

UsedRange can be useful for inspecting a worksheet, but I do not use it as my first choice for business data boundaries. It can include cells that were previously edited or formatted, even after the visible content is gone.

lastRow = ws.UsedRange.Row + ws.UsedRange.Rows.Count - 1

That returns the bottom row of Excel’s used range, not necessarily the last row of the dataset you intend to process.

SpecialCells(xlCellTypeLastCell)

SpecialCells(xlCellTypeLastCell) is fast, but it reports Excel’s last used cell, which is affected by the same used-range behavior. It may point to a formatted or formerly used area rather than the last current record.

lastRow = ws.Cells.SpecialCells(xlCellTypeLastCell).Row

Use this when you intentionally need Excel’s internal last-cell marker. Do not use it as a drop-in replacement for the last populated row in a required data column.

xlDown

I avoid Range("A1").End(xlDown).Row for last-row detection. It works only when the first cell is populated and the data below it is contiguous. If there is a blank cell inside the column, xlDown stops early. If the starting cell is blank, it can jump to an unexpected row.

Starting from the bottom and moving up is safer for ordinary data tables.

Reusable Last-Row Function

If several macros need the same check, I make it a small function that accepts the worksheet and column. That keeps the calling code readable and prevents repeated unqualified references.

Function LastRowInColumn(ByVal ws As Worksheet, ByVal columnLetter As String) As Long
    Dim lastRow As Long

    lastRow = ws.Cells(ws.Rows.Count, columnLetter).End(xlUp).Row

    If lastRow = 1 And Len(ws.Cells(1, columnLetter).Value) = 0 Then
        LastRowInColumn = 0
    Else
        LastRowInColumn = lastRow
    End If
End Function

Example use:

Sub UseLastRowFunction()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = LastRowInColumn(ws, "A")

    If lastRow = 0 Then Exit Sub

    ws.Range("A1:D" & lastRow).Copy _
        Destination:=ThisWorkbook.Worksheets("Report").Range("A1")
End Sub

Last Row Checklist

  • Use .End(xlUp) when one column reliably identifies every data row
  • Use Find when the last row may appear in any column on the sheet
  • Use a selected-column scan when only certain columns should define the dataset
  • Treat a returned row of 1 carefully if the column can be empty
  • Avoid unqualified Cells, Rows, Range, and Columns references in production macros
  • Do not rely on UsedRange or SpecialCells(xlCellTypeLastCell) unless you specifically want Excel’s used-range boundary

Getting the boundary right is small code, but it protects every later step in the macro: loops, copy ranges, formulas, sorts, and imports all depend on knowing where the data actually ends.

Frequently Asked Questions

Finding the last row in Excel using VBA is usually a range-boundary problem: choose the column or worksheet area that defines the data, then handle empty inputs deliberately.

What is the most efficient method to determine the last non-empty row in a specific Excel column using VBA?

For a specific required column, I use the worksheet-qualified .End(xlUp) pattern:

Dim ws As Worksheet
Dim lastRow As Long

Set ws = ThisWorkbook.Worksheets("Data")
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

This is fast and clear when column A is populated for every valid record. If column A might be empty, check whether lastRow = 1 and A1 is blank before treating row 1 as data.

How can I automate copying a range of data up to the last populated row in Excel with VBA?

To copy data to the last row, I first determine the last row, then use that in my copy range. Here’s a simple snippet:

Dim sourceWs As Worksheet
Dim targetWs As Worksheet
Dim lastRow As Long

Set sourceWs = ThisWorkbook.Worksheets("Data")
Set targetWs = ThisWorkbook.Worksheets("Report")

lastRow = sourceWs.Cells(sourceWs.Rows.Count, "A").End(xlUp).Row
sourceWs.Range("A1:C" & lastRow).Copy Destination:=targetWs.Range("A1")

This copies columns A to C through the last populated row in column A.

What formula can I use in VBA to find and select the last row of data across multiple columns?

I usually use Find when I need the last used row across the worksheet:

Dim lastCell As Range
Dim lastRow As Long

Set lastCell = ws.Cells.Find(What:="*", _
                             After:=ws.Cells(1, 1), _
                             LookIn:=xlFormulas, _
                             LookAt:=xlPart, _
                             SearchOrder:=xlByRows, _
                             SearchDirection:=xlPrevious, _
                             MatchCase:=False)

If Not lastCell Is Nothing Then lastRow = lastCell.Row

This checks the sheet by rows and avoids assuming the final record is in one particular column.

Could you describe a technique for locating the final row in a dataset that includes blank rows using Excel VBA?

Blank rows in the middle are fine if the key column has data after them. Start from the bottom of the key column and move up:

lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

If data may appear below the key column in other columns, use the Find method across the worksheet instead.

What is the process for identifying the last used row in a worksheet using the xlUp method in VBA?

The xlUp method is straightforward. I typically use it like this:

lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

This finds the last used row in column A on ws. For multiple columns, repeat it for the columns that define your dataset and take the maximum.

How can I programmatically find the last completely blank row in an Excel spreadsheet using VBA?

If you mean the first row after your data table, add 1 to the last used row in the key column:

lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
blankRow = lastRow + 1

If you need a completely blank row across several columns, check that range with Application.CountA(ws.Range("A" & blankRow & ":D" & blankRow)) = 0 before writing to it.

Written by

Allen Hoffman

Contributor, Excel TV

  • Lookup Functions
  • Data Manipulation
  • Keyboard Shortcuts
  • Workflow Efficiency
Allen Hoffman is a contributor to Excel TV focused on practical Excel techniques for everyday data work. His tutorials cover topics including lookup functions, data manipulation, cell formatting, keyboard shortcuts, and workflow efficiency. Allen's writing aims to make common Excel tasks clearer and faster, with step-by-step guidance suited to analysts and professionals who use Excel regularly in their work.

Read more articles by Allen Hoffman

Editorial standards

Fact Checking & Editorial Guidelines

Every article on Excel TV is held to a published editorial standard. The goal: accurate, current, and useful — without filler.

  1. Expert review.Drafts on technical Excel topics are reviewed by a contributor with hands-on, working knowledge of the feature being covered.
  2. Source validation.Claims about Excel behavior are tested in current Microsoft 365 builds. Third-party product claims are sourced from the vendor's own documentation.
  3. Disclosure.Affiliate links, sponsorships, and any commercial relationships that influenced a piece are disclosed in-line and at the foot of the article.
  4. Updates.Articles are revisited when Microsoft ships changes that affect the content. The most recent revision date is shown on every post.

Spot a problem? Email editor@excel.tv and we will look at it.

Subject-matter review

Reviewed by Subject Matter Experts

Technical Excel articles are reviewed by contributors with verifiable, hands-on experience in the topic — not generalist editors.

  • Qualified reviewers.Reviewers include Microsoft Excel MVPs, working business-intelligence practitioners, and Excel TV editorial staff. See each author's page for credentials.
  • Current to a known Excel build.Procedural articles state which Excel version they were validated against. Where Microsoft has since changed behavior, the article carries an inline update note.
  • Clarity check.Reviewers verify steps are reproducible by a reader at the assumed skill level — not just technically correct in a vacuum.

Want to contribute or review for Excel TV? See the about page.