Based on the value of what is in column B (column A is hidden) it groups/colors/removes the corresponding node(s). R -  values are nodes that need to be grouped. The rows are 6 cells apart before a new node starts. It also adds a formula below the value.I wrote this code and it of course looks messy. I just want to know if there is a way to make it a little easier to understand.  It's not the best when it comes to recursion and I need to explain this to a few peers so they will be able to keep the model running as well.Example:Code for Review:    'Set values to 5; first row to begin grouping    Start_Row = 5    Last_Row = 5 Application.Wait Now + TimeValue(00:00:02) Application.StatusBar = Grouping and formating workgroups...While WorksheetFunction.CountA(Rows(Start_Row)) <> 0   'Color code certain ranges based on ending values of functions/nodes   If (Right(range(B & Start_Row).Value, 10) = - Overhead Or Right(range(B & Start_Row).Value, 14) = - Non Overhead Or Right(range(B & Start_Row).Value, 4) = - WG) Then        Finish_Row = Start_Row - 1        Last_Row = Start_Row + 6    End If    With ActiveSheet    lastRow_das_delete = .Cells(.Rows.Count, A).End(xlUp).Row    End With    'Delete actual and support nodes, loop twice to remove R value rollups    For double_actuals_support = 0 To lastRow_das_delete    range(B & (Start_Row + 1)).Formula = =CONCATENATE(~  & INDEX('Complete Database Pull'!B:B,MATCH(B & Start_Row & ,'Complete Database Pull'!C:C,0)))    If Right(range(B & Start_Row).Value, 19) = - Actuals & Support Then        For adjust_delete = 0 To 5            Rows(Start_Row).EntireRow.Delete        Next adjust_delete    End If    Next double_actuals_support    'Group R- nodes together    If Left(range(B & Start_Row).Value, 2) = R- And Start_Row <> 5 And Last_Row <> Start_Row Then        Finish_Row = Start_Row - 1        Rows(Finish_Row & : & Last_Row).Group        Last_Row = Start_Row + 6        'If R- is in the first row, don't group up    ElseIf Left(range(B & Start_Row).Value, 2) = R- And Start_Row = 5 Then        Last_Row = Start_Row + 6    End If    'Color WG rows/columns    If (Right(range(B & Start_Row).Value, 10) = - Overhead Or Right(range(B & Start_Row).Value, 14) = - Non Overhead) Then        range(B & Start_Row & :Y & (Start_Row + 3)).Interior.Color = 15853276        range(B & Start_Row & :B & (Start_Row + 5)).Interior.Color = 15853276    End If    If Right(range(B & Start_Row).Value, 4) = - WG Then        range(B & Start_Row & :Y & (Start_Row + 3)).Interior.Color = 15523812        range(B & Start_Row & :B & (Start_Row + 5)).Interior.Color = 15523812        Last_Row = Start_Row + 6    End If    'Adjust Start_Row 6 rows    Start_Row = Start_Row + 6Wend
Groups/colors/removes cells
recursion;vba;excel
Side-scrollingDon't make do it. If a line of code doesn't fit comfortably inside the code pane of the IDE with a reasonable window size, it's too long. If the code line actually has to be that long, take advantage of VBA's line continuation operator (_) and break it up so you can see the whole line at once. The If tests are especially problematic, given that by the time I've scrolled right far enough to see the last condition I can't see what gets executed if it's true.NamingAlthough the actual naming convention you use is a question of taste, it is good to be consistent. I typically follow a convention similar to the one outlined over on the SO documentation page for Naming Conventions. You have a mix of Pascal and lower snake case for (what appear to be) local variables - i.e. Start_Row and double_actuals_support.More important is the variables named *_Row. You have Start_Row, Last_Row, and Finish_Row,  These are frankly confusing as to what they are referring to. All of those names imply that they are more or less constant values, but all of them change.  I think of a start row as the first row on a sheet and last row as the last row on the sheet.  Since Start_Row is the holding your loop counter, I'd suggest something more like workingRow or currentRow. Last_Row appears to refer to the last row of each node (it is always offset by 6), so it would be more appropriately named something like nodeEndRow.  Finally Finish_Row appears to be set to the last row of the previous node, so something like previousNodeEnd would be appropriate.Best PracticesIndentation isn't consistent, but it's hard to tell whether that's an artifact of copying and pasting or not.  You can fix it with Rubberduck's SmartIndenter feature (full disclosure, I'm a contributor).You should qualify all of your Range and Cells calls to avoid using the global collections (if the code isn't in a worksheet module). Wrap the whole thing in With ActiveSheet (or even better, a worksheet code name like With Sheet1) if it's anywhere other than a worksheet module, otherwise wrap the whole thing in With Me.Use the String returning versions of functions (they end with $) instead of Variant returning functions when they are available to avoid implicit casts. You can find a list of them here.Declare your variables and add Option Explicit to your modules.  Otherwise they are implicitly Variant, and this can cause a host of issues. If they are declared somewhere above the code that was posted, they should be moved closer to where they are being used.  For example, double_actuals_support and adjust_delete are what appear to be throw-away loop counters. There's no reason you should need to go somewhere else to see how they're defined.It's not clear why you call Application.Wait Now + TimeValue(00:00:02) at the start.  If there's a reason you need to do that, comment it.  Also, while updating the status bar is useful, you should immediately call DoEvents after updating it so that the screen can refresh.  Although, it's possible you wont' need it once the row delete issue is addressed, which leads me to...Skip the row delete gymnasticsI literally had to stare at this section of code for about 5 minutes before I could determine that it probably wasn't actually a bug (and I can still only make an educated guess as to why it's there):'Delete actual and support nodes, loop twice to remove R value rollupsFor double_actuals_support = 0 To lastRow_das_deleterange(B & (Start_Row + 1)).Formula = =CONCATENATE(~  & INDEX('Complete Database Pull'!B:B,MATCH(B & Start_Row & ,'Complete  Database Pull'!C:C,0)))If Right(range(B & Start_Row).Value, 19) = - Actuals & Support Then    For adjust_delete = 0 To 5        Rows(Start_Row).EntireRow.Delete    Next adjust_deleteEnd IfNext double_actuals_supportFirst, the comment is extremely misleading - this doesn't loop twice, it loops through the every row of the worksheet. As it looks like you found out, deleting rows while you're looping through them alters the row index.  From the above section of code, it appears that you are just reloading the the worksheet from that point down if you have to delete something. This is incredibly inefficient.  There are 3 much easier and much faster ways to handle this.  Either loop through the rows backwards, accumulate the rows that need to be deleted and delete them all at once when you're done, or conditionally increment your row counter.  If I'm misunderstanding the purpose of this code, you do have a bug, because there isn't anything else in your code that adjusts the indexing for deleted rows.Note that this is the only thing I could find that indicated recursion in the code you posted.  There aren't any function calls and I wouldn't know the name of the posted function anyway.Avoid repeated function call and Worksheet accessesIf the return value of a function call or property is guaranteed to be identical over multiple calls, you should cache the value in a local variable instead of repeatedly requesting it.  This line is a perfect example (line continuations added for clarity): If (Right(range(B & Start_Row).Value, 10) = - Overhead Or _      Right(range(B & Start_Row).Value, 14) = - Non Overhead Or _     Right(range(B & Start_Row).Value, 4) = - WG) ThenThe value of Range(B & Start_Row).Value is going to be exactly the same for the 3 times you're requesting it. This would be much better:Dim pulled As Stringpulled = Range(B, workingRow).ValueIf (Right(pulled, 10) = - Overhead Or Right(pulled, 14) = - Non Overhead Or _    Right(pulled, 4) = - WG) ThenNote that your lines are getting shorter now too.Repeated codeThere's a consistent pattern that you repeat over and over again.  Going back to the same line of code as above, you make multiple tests to see if a given string ends with another string similar to this:If (Right(range(B & Start_Row).Value, 10) = - Overhead ...I'd throw in a couple simple utility functions called EndsWith and StartsWith and use those instead (StartsWith would be similar):'Tests to see if a string ends with any of the passed candidates.Public Function EndsWith(test As String, ParamArray candidates() As Variant) As Boolean    Dim candidate As Variant    For Each candidate In candidates        Dim chars As Long        chars = Len(candidate)        If Len(candidate) <= test Then            If Right$(test, chars) = candidate Then                EndsWith = True                Exit Function            End If        End If    NextEnd FunctionThis has several advantages:It makes the code much clearer as to the intent. It gets rid of all of the hard coded string lengths that are prone to errors. It almost completely solves the side scrolling issue. It short-circuits on the first match (If x Or y Or z doesn't).The line referenced above can now be something like:If EndsWith(pulled, - Overhead, - Non Overhead, - WG) ThenUse numeric column indexes and constantsThe only reason that Excel has letters for column identifiers is because they are easier for humans to read - it ultimately treats column indexes as a numbers internally. So, it's a (albeit small) performance improvement to use numeric range indexing rather than string indexing because Excel just has to parse it.  Also, for individual cells, using .Cells is much better than using the .Range because Excel doesn't have to do any parsing.'GoodRange(B, workingRow).Value'BetterCells(workingRow, 2).ValueWhile it's easier to see B than to have to mentally calculate that it would be column 2 , it's important to remember that B is just as much of a magic letter as 2 is a magic number.  You can make code much more readable by defining and using constants for them. This also makes the code tremendously easier to maintain - think about how many places you would need to make changes if your columns all shifted one to the right.  If it's in a constant, you only need to change it in one place:'Module levelPrivate Const ROW_INDEX_COLUMN As Long = 1Private Const DATA_ID_COLUMN As Long = 2Private Const END_COLUMN As Long = 25'...'BestCells(workingRow, DATA_ID_COLUMN).ValueAnother perfect candidate for a constant would be NODE_LENGTH As Long = 6.Extract methods for common codeThe code used for formatting the cells is pretty much identical other than the colors being used.  Instead of repeating the code twice here...'Color WG rows/columnsIf (Right(range(B & Start_Row).Value, 10) = - Overhead Or Right(range(B & Start_Row).Value, 14) = - Non Overhead) Then    range(B & Start_Row & :Y & (Start_Row + 3)).Interior.Color = 15853276    range(B & Start_Row & :B & (Start_Row + 5)).Interior.Color = 15853276End IfIf Right(range(B & Start_Row).Value, 4) = - WG Then    range(B & Start_Row & :Y & (Start_Row + 3)).Interior.Color = 15523812    range(B & Start_Row & :B & (Start_Row + 5)).Interior.Color = 15523812    Last_Row = Start_Row + 6End If...it would be more readable and maintainable if you made a separate Sub that performed the formatting.  Something like this:Private Sub FormatDataNode(anchor As Range, colorIndex As Long)    Dim r As Long, c As Long    r = anchor.Row    c = anchor.Column    With anchor.Parent        .Range(.Cells(r, c), .Cells(r + 3, END_COLUMN)).Interior.Color = colorIndex        .Range(.Cells(r, c), .Cells(r + 5, c)).Interior.Color = colorIndex    End WithEnd SubThen you can simplify the code in the loop to something more like this:Private Const WG_COLOR As Long = 15523812Private Const NON_WG_COLOR As Long = 15853276'...'Color WG rows/columnsIf EndsWith(pulled, - Overhead, - Non Overhead) Then    FormatDataNode Cells(workingRow, DATA_ID_COLUMN), NON_WG_COLORElseIf EndsWith(pulled, - WG) Then    FormatDataNode Cells(workingRow, DATA_ID_COLUMN), WG_COLOR    nodeEndRow = workingRow + NODE_LENGTHEnd IfConclusionOverall nothing horrible - the suggestions above will make it much more readable.  My guess is that taking a different strategy to deleting rows will have a huge impact in both performance and correctness.  After that's sorted out, my guess is that this code can be simplified tremendously - feel free to post a follow-up question after your first refactoring round.