How to Program CNC M99 Loops
M99 ends a subprogram and sends control back to the parent program. Get the call, the repetition count, and the return wrong and you scrap the part or break a tool. This guide is for machinists and process engineers who write or debug their own code. Read it and you can tell whether a loop belongs in the program or in CAM output.

In this article
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Key takeaways
What M99 actually does when you program CNC M99 loops
M99 is a control instruction, not a motion command. When the control reads it, the current subprogram ends and execution returns to the block right after the M98 call that started it. That is the whole job: one return, no waiting, no spindle change. If the loop is running in a main program rather than a subprogram, M99 rewinds to the first block and the cycle starts again from the top.
This matters because the same word does two very different things depending on where it sits. At the end of O3001 called by M98, M99 hands control back to the parent. At the end of the main program O0001, M99 makes the machine run the part over and over until someone presses Reset. Both are legal. Only one is usually intended.
The pair you will write most often is M98 to call and M99 to return. A subprogram is stored under its own O number, exactly like a main program. The call block names the subprogram and, optionally, how many times to repeat it. The control handles the stack of return addresses internally, so you never need to track where you came from.
One practical note: M99 does not reset modal states on its own. Feed rate, spindle speed, tool offsets and work coordinates carry back into the parent program. If your subprogram changes G54 to G55 and returns without switching back, the next operation cuts in the wrong place. Restore any modal value you touched before the return block.
- 1Parent programCalls the subprogram with M98 P….L… and continues after the call returns.
- 2SubprogramHolds the repeated geometry and ends with M99. Its own O number sits on the first line.
- 3Return addressManaged by the control. You do not write it, and you cannot skip past it.
- 4Modal statesNot restored automatically. Reset feed, speed and offsets yourself.
When a subprogram loop makes sense and when CAM should handle it
Reach for M98 and M99 when the same geometry repeats at known offsets: a row of holes, a set of pockets on a bolt circle, or a family of parts that differ only in work offset. The subprogram holds one pattern. The main program shifts the work coordinate or adds a G52 local shift, calls the pattern, and moves on. The controller does the arithmetic, and your file stays short enough to read at the machine.
A hole pattern on a 300 mm bolt circle is the classic case. One subprogram drills one hole at a known position. The main program loops it 12 times with a G52 shift, or the subprogram itself indexes a rotary table. Change the hole depth once and every hole follows. That is the real benefit: one edit point instead of twelve.
Skip the loop when the geometry is unique per feature. If each pocket has a different shape or depth, a subprogram just adds indirection. Write it out or let CAM post it. You will spend more time debugging offsets than the shorter file saves.
Loops also make bar-fed and pallet work easier. M99 at the end of the main program returns to the top, so the machine runs the same part until the bar runs out. Combine it with a parts counter and a conditional skip if the control supports M codes for that. If it does not, leave it out. An unattended loop with no counter is how you run a tool into a finished part at 3 a.m.
- 1Good fitRepeating patterns, bolt circles, families of parts with offset shifts.
- 2Weak fitUnique geometry per feature. Loop overhead outweighs the savings.
- 3Bar feedM99 at the end of the main program repeats until the bar is consumed.
- 4SafetyAdd a parts counter or a skip before you walk away from a looping program.
Control differences you need to check before you program CNC M99 loops
The Fanuc pattern is the reference: M98 P3001 L4 calls O3001 four times, M99 returns. Most controls follow it closely, but the details drift. On some, the L word must sit before the P word. On others, the call takes a full file name rather than a four-digit O number. Read the manual for the specific control on the floor, not a forum post.
Nesting depth is the first thing to confirm. Fanuc-style controls usually allow four levels of subprogram nesting. That is four subprograms calling each other, not four repetitions of one. Some controls allow eight, some allow two. If you exceed the limit, the control alarms out at the call block and stops. Keep real programs to two levels and you will never hit it.
Repetition limits vary too. The L count is usually a two- or three-digit value. Values above the control limit trigger an alarm rather than silently truncating. For 10,000+ part runs we keep the loop count modest and let the bar feeder or a macro handle the long count.
Watch for controls that treat M99 differently at the end of a main program. On most it rewinds and repeats. On a few it behaves like M30 and ends. If you rely on the rewind behavior, test it on the machine with the spindle stopped before you trust it in production.
- 1Word orderP and L order varies. Check the manual for the exact block format.
- 2NestingTypically four levels. Stay at two for production work.
- 3Repeat countL is capped by the control. High counts may alarm instead of truncating.
- 4End-of-main M99Rewinds on most controls. Confirm on yours before relying on it.
Common M99 mistakes and how they show up on the machine
The most common failure is a missing M99. The control reaches the end of the subprogram, finds no return, and alarms out. The fix is one line, but finding it under a pile of nested calls takes longer than writing it. Put M99 on its own block at the end of every subprogram and never bury it in a conditional.
The second is an infinite loop. M99 at the end of a main program with no counter runs forever. The symptom is a machine that keeps cutting after the part is done. Hit Feed Hold and Reset, then add a parts counter or a conditional skip. Never leave a looping program unattended without one.
A wrong return address shows up as the tool cutting in the wrong place after the first loop. That is almost always a modal state carried back from the subprogram: a changed work offset, a changed plane, or a feed rate left in inches per minute when the parent expects per revolution. Restore every modal value you touched before the return block.
Finally, watch the L count on a subprogram that indexes a rotary table. If the subprogram rotates the table and M99 returns, the next call starts from the new position. That is usually what you want, but it is not what a programmer who forgot the index expects. Write the index, the cut, and the return as three separate ideas so the next person can read it.
- 1Missing M99Control alarms at the end of the subprogram. Add the return block.
- 2Infinite loopMain program rewinds with no counter. Add a parts counter or skip.
- 3Wrong position after loopA modal state carried back. Restore offsets, plane and feed mode.
- 4Rotary index surpriseSubprogram rotates the table; next call starts from the new angle.
How to write and prove out an M99 loop
Work through these in order. Do not skip the dry run.
- 11. Write the subprogram and end it with M99Give it its own O number, for example O3001. Put the repeated geometry inside. End with M99 on its own block. Do not change work offsets inside the subprogram unless you restore them before the return.
- 22. Call it from the main program with M98Use M98 P3001 L4 to run it four times. If your control wants the L word first, write L4 P3001. Start with L1 so you can watch one pass before committing to the full count.
- 33. Set the offset or shift between passesUse a G52 local shift or a work coordinate change in the main program. Keep the step value equal to your pattern pitch. A 50 mm pitch with a 50.1 mm shift will show up as a growing error by the last hole.
- 44. Dry run with the tool clearRaise Z by 50 mm, set rapid override low, and run the full loop count. Watch the distance-to-go display on each return. The tool should come back to the same point every pass.
- 55. Check nesting depth before you add another levelCount the subprograms that call each other. Stay at two levels for production. If you need three, confirm the control limit first. Exceeding it alarms at the call block.
- 66. Cut the first part at reduced feedRun at 50 percent feed override for the first loop. Measure the first and last feature. If they match, the loop logic is sound. If they drift, the shift value is wrong.
- 77. Add a parts counter for unattended runsUse a macro counter or a conditional skip if the control supports it. A looping program with no counter will keep cutting after the last part. Do not walk away without one.
M99 loop patterns and when to use each
Pick the pattern that matches the job. Do not stack patterns you do not need.
| Pattern | Code shape | Use when | Watch out for |
|---|---|---|---|
| Simple repeat | M98 P3001 L4 | Same geometry, same position, four times | No shift between passes. Tool recuts the same spot. |
| Offset repeat | G52 shift, then M98 | Bolt circle or row of holes | Shift must equal pattern pitch exactly. Drift compounds. |
| Rotary index | Subprogram indexes table, ends M99 | Features around a cylindrical part | Next call starts from the new angle. Confirm that is intended. |
| Bar feed loop | M99 at end of main program | Runs until the bar is consumed | No counter means it runs forever. Add one. |
| Family of parts | M98 with different work offsets | Same shape, several fixtures | Work offset must be restored before the return block. |
Keep the loop simple and prove it dry
Two nesting levels, one edit point, and a dry run with the tool clear will catch nearly every M99 problem before it reaches the spindle. If the geometry is not truly repeating, write it out instead.
Frequently asked questions
What is the difference between M98 and M99?
M98 calls a subprogram. It names the subprogram with a P word and optionally sets a repeat count with an L word. Execution jumps into the subprogram at that point.
M99 ends the subprogram and returns control to the block after the M98 call. At the end of a main program, M99 rewinds to the top instead. Same word, two behaviors, decided by where it sits.
How many times can I repeat a subprogram?
It depends on the control. The L count is usually a two- or three-digit value, so most controls accept up to 99 or 999 repeats before the word overflows.
For long production runs, keep the loop count modest and let a bar feeder or a macro handle the rest. Pushing the L limit to its maximum is a good way to trigger an alarm mid-run.
Can I nest subprograms inside subprograms?
Yes, up to the control limit. Fanuc-style controls typically allow four levels of nesting. Some allow eight, some allow two.
For production work, stay at two levels. Each extra level makes the return stack harder to debug and adds no benefit to most jobs. Check the manual before you go deeper.
Why does my tool cut in the wrong place after the first loop?
A modal state from the subprogram carried back into the parent. The usual suspects are a changed work offset, a changed plane, or a feed mode left in the wrong units.
Restore every modal value you touched before the M99 return block. If the subprogram changes G54 to G55, switch it back. If it selects G91 incremental, return to G90 absolute.
Is M99 the same as M30?
No. M30 ends the program, resets the program to the top and, on most controls, clears the modal state. M99 at the end of a main program rewinds and repeats without clearing modals.
At the end of a subprogram, M99 is purely a return. M30 in a subprogram would end the whole job, which is almost never what you want.
Can I use M99 loops on a mill-turn or 5-axis machine?
Yes. The loop logic is the same. What changes is the number of axes you need to restore before the return. On a 5-axis machine, a subprogram that tilts the rotary axes must return them to a known orientation before M99.
On a mill-turn center, check whether the subprogram runs in the turning mode or the milling mode, and make sure the mode is correct when control returns to the parent.
Send us the drawing and we will quote it
We machine prototypes and production runs from one part to 10,000+, with 5-axis, 4-axis and mill-turn capacity and 100% inspection before shipment.
12-hour quote100% inspectionNo minimum order quantity