How to use VBA to quickly and easily delete unfiltered rows

Recently I ran into a situation where an Excel file was so large it would not copy only filtered rows to a second sheet. Attempting to do so caused Out of Memory errors. I needed to come up with something that was fast, so processing row by row was not an option. Below is the code that I came up with. It deleted 20,000+ rows in just a couple of seconds. The secret was to keep track of contiguous hidden rows and delete them as a block instead of one at a time.

By processing from the last row to the top, I could still iterate the rows without impacting anything as I deleted unfiltered rows. Here is Immediate Window output for reference purposes.

  • 1 Rows( 22798 : 29617 )
  • 2 Rows( 22784 : 22796 )
  • 3 Rows( 22765 : 22782 )
  • 4 Rows( 22749 : 22763 )
  • 5 Rows( 22733 : 22747 )
  • 6 Rows( 22717 : 22731 )
  • 7 Rows( 22701 : 22715 )
  • 8 Rows( 22685 : 22699 )
  • 9 Rows( 22671 : 22683 )
  • 10 Rows( 22660 : 22669 )
  • .
  • .
  • .
  • 99 Rows( 3486 : 3511 )
  • 100 Rows( 2931 : 3484 )
  • 101 Rows( 2886 : 2929 )
  • 102 Rows( 2816 : 2884 )
  • 103 Rows( 1282 : 2814 )
  • 104 Rows( 1257 : 1280 )
  • 105 Rows( 901 : 1255 )
  • 106 Rows( 886 : 899 )
  • 107 Rows( 773 : 884 )
  • 108 Rows( 727 : 771 )
  • 109 Rows( 358 : 725 )
  • 110 Rows( 4 : 356 )

Please, test the next way. It will check the rows visibility of A:A range and create a Union range containing the hidden cells. Then, delete the range EntireRow at the end:

Sub removeHFrows()
  Dim sh As Worksheet, lastR As Long, lHeaderRow As Long, rngA As Range, rngH As Range, i As Long
  
  Set sh = ActiveSheet ' Use here the sheet you need
  lastR = sh.Range("A" & sh.Rows.count).End(xlUp).row    'supposing that A:A is not shorter then other columns...
  lHeaderRow = 3
  Set rngA = sh.Range("A1:A" & lastR)
  
  For i = 3 To lastR
        If rngA.Rows(i).Hidden Then
            If rngH Is Nothing Then
                Set rngH = rngA(i)              'firstly set the range
            Else
                Set rngH = Union(rngH, rngA(i)) 'make a Union between the previous cells in the range and this one
            End If
        End If
  Next i
  'delete the Union range rows, at once:
  If Not rngH Is Nothing Then rngH.EntireRow.Delete
End Sub

Alternatively count the number of hidden rows in each block and use Resize().

Private Sub RemoveHiddenRows(Sh As Worksheet, lHeaderRow As Long)

    Dim startrow As Long, endrow As Long
    Dim n As Long, i As Long, t As Long
    Dim t0 As Single: t0 = Timer
     
    n = 0
    With Sh
        If .AutoFilterMode = False Then Exit Sub
        startrow = .AutoFilter.Range.Row
        endrow = .AutoFilter.Range.Rows.Count + startrow - 1
        For i = endrow To startrow Step -1
            If .Rows(i).Hidden Then
                n = n + 1 ' count hidden rows
            ElseIf n > 0 Then
                .Rows(i + 1).Resize(n).Delete Shift:=xlUp
                t = t + n
                n = 0
            End If
        Next
        .AutoFilter.ShowAllData
    End With
    
    MsgBox t & " rows deleted", vbInformation, _
           Format(Timer - t0, "0.0 secs")
End Sub