One of the most annoying things about LaTeX is that handling errors can be cumbersome and nerve-racking. In fact, it is the main reason that keeps me from actively recommending LaTeX to most people (not only Apple users). Handling LaTeX errors is not something for the average user.

The main reason for this, I believe, is that error messages tend to be cryptic and often outright misleading. The 'crypticness' problem can be solved by learning their basic structure. But the 'missleadingness' is a real problem.

For example, yesterday I struggled for more than an hour with a variety of error messages from biber such as this one:

Entity: line 2004: parser error : Extra content at the end of the document <bcf:citekey order="31">AuthorYear</bcf:citekey> ^

And from pdflatex, such as this one:

! pdfTeX error (font expansion): auto expansion is only possible with scalable 
\@EveryShipout@Output ...@Org@Shipout \box \@cclv 


But my question here does not concern these errors in particular. They are just illustrations for just how misleading these error descriptions can be. Because the problem that cause all this was simply that I had written

\textsf{bla bla bla}

instead of

\enquote{bla bla bla}

And I might add: that mistake was in line 918, so even the line numbers indicated in these error messages are rather far off (ok, biber is not referring to the .tex-file, but I had to learn that too).

I leave it to the geeks to figure out the logic that led to these error messages (cause I'm sure there is a (hidden) logic. But for those who wonder how on earth I could confuse \textsf with \enquote: the answer is: I did not type it, I used the (wrong) keyboard shortcut.

Now, call me stupid or whatever. This is just an example.

My point is: what strategies are there to systematically track down the source of errors when compiling a LaTeX document in an efficient way?

And let me add a more specific version of that question: Assuming that one strategy is to use some sort of version control system to go back to the last error free version of the file and identify the changes that cause the error, is there any automated or semi-automated way to do this. For example, I have my files on dropbox, which allows me to access previous versions of the file up to 30 days back. However, given that a new version is stored every time I hit "save", retrieving each version, saving it, and then loading it into my LaTeX editor to compile it seems rather unfeasible. Not to speak of the problem of relative paths when the old version is not saved in the same folder.

  • 2
    Well the font expansion error is not misleading: One of your font commands (probably the textsf) pointed to a bitmap font which is not scalable and getting rid of such of an error normally involves installation of some fonts. But it is completly impossible for some error recovering strategy to infer from such an error that you actually didn't want to write the (valid) command \textsf. Actually you were quite lucky to get an error which pointed you to the typing error. Commented Jul 25, 2013 at 13:38
  • 5
    @UlrikeFischer OK, so you are suggesting to file that one under "learn your LaTeX basics"? - I'd say that goes far beyond basics. I'd consider myself an advanced LaTeX user (wrote my PhD in LaTeX), but I don't even understand the steps of reasoning that apparently could have brought me closer to the actual typing error. In my mind, it was completely misleading, since I had not fiddled with my microtype settings for months. - Yes, I agree: finding errors is easier if you know LaTeX in an out. But what if you don't?
    – Christoph
    Commented Jul 25, 2013 at 15:21
  • 1
    No I'm filing this error under "you need your brain and luck". You need to change your point of view and realize that the question is not "how to I get rid of an font error" but "how can I get rid of whatever triggered the use the problematic font?". And then you need to find a answer to the second problem which is much more difficult than the first as fonts are called all over a document. And if you don't find the answer: Ask someone else. That's what I do when I don't find a solution. Commented Jul 25, 2013 at 15:43
  • 1
    Re: finding the "last good/first bad" version, I can add only two words: git bisect...
    – mbork
    Commented Mar 15, 2014 at 12:36
  • 1
    See also: tools - How to best debug LaTeX? - TeX - LaTeX Stack Exchange
    – user202729
    Commented Dec 19, 2021 at 3:13

3 Answers 3


I'm not sure you want to read this ...

Only the first error LaTeX throws is important because the first error very often causes following errors which vanish after you corrected the first one.

So if you have to find errors

  • write only small parts of your code without compiling!
  • compile,
  • look only for the first error (usually located in the small part of new code),
  • find it (okay, that could be a hard job, but you will be better with more experience) and
  • correct it. Ignore the other errors. Now
  • compile again (and you will have usually not only one error less),
  • look only for the first error,
  • ...

I am used to compile each time I am thinking about my next paragraph to write. So I have only a few lines with new code and be able to locate errors faster.

If you have a lot of code to correct insert after some lines \end{document}, compile and check the errors. If everything is okay, move \end{document} some lines down, recompile and so on.

As @HaraldHanche-Olsen said in his comment:

"If you suspect your error can be anywhere inside a large-ish block of code, performing a binary search is often useful. With the \end{document} trick, instead of moving \end{document} by a few lines at a time, try putting it roughly halfway between the location which produces the error and one that doesn't.

I often comment out a block using \iffalse\fi instead. Of course, with this technique, you must take care not to include part of an environment."

If you are using \input{...} or \include{...} in your document check with commenting it if it includes error(s). If so, you can use the macro \endinput to achieve a similar effect to \end{document} (see comment from @JuanA.Navarro).

If you need to use a version control for your TeX file please use a real system to use different versions like svn (subversion) ... Search this website for svn.

  • 14
    I concur with the "compile often" philosophy Commented Jul 25, 2013 at 11:25
  • 7
    If you suspect your error can be anywhere inside a large-ish block of code, performing a binary search is often useful. With the \end{document} trick, instead of moving \end{document} by a few lines at a time, try putting it roughly halfway between the location which produces the error and one that doesn't. I often comment out a block using \iffalse\fi instead. Of course, with this technique, you must take care not to include part of an environment. Commented Jul 25, 2013 at 11:36
  • 2
    Just two cents: if one doesn't know any VCS, learning svn may seem a bad idea; why not learn Git or Mercurial instead?
    – mbork
    Commented Mar 15, 2014 at 12:50
  • 1
    @Christoph As a programmer, I find that perspective appalling. Sure, there are cases where you can't always compile because you know you'll get an error anyway as your "code" is incomplete, but waiting a very long time to compile is an easy way to get a huge list of error messages that you then have to go through and resolve all at once rather than resolving as you go along, regardless of how simple it may be to identify and fix the causes.
    – JAB
    Commented May 6, 2016 at 18:34
  • 4
    Splendid answer. Since this was posted, I presented a talk at a TUG meeting on debugging LaTeX files. It's written up in aTUGboat article: tug.org/TUGboat/tb38-2/tb119beet.pdf Perhaps this might be a useful additional reference. Commented Apr 19, 2020 at 15:46
  1. Many editors can be convinced to jump automagically to the first error.
  2. If you don't understand the error message, just google it; at least, that's what I do.
  3. Quite often an error in the preamble causes a first error somewhere in the text. Examples: error within code to define a heading will show up after first pagebreake, error in code to change the section will often not be shown before the first section in the text occures. So if you stumble upon an unrelated error message, look whether around the line of the text which the error message provides a special element has been used, maybe for the first time. Go to its definitions in the preamble and search there.
  • 5
    "If you don't understand the error message, just google it; at least, that's what I do." The point is that the error message usually has many lines, with a ton of useless trace that might be either very generic or highly dependent on current input. You can't throw a ton of (potentially irrelevant) error lines and google it.
    – SOFe
    Commented May 19, 2019 at 8:45

This answer is written mostly in the context of a TeX developer.
In my experience, different kinds of bugs requires very different strategy to debug.
Thus, not exactly what OP asks about; nevertheless I try to be comprehensive.

I guess there's a bit of overlap with https://tex.stackexchange.com/a/516657/250119.

The general approach for debugging is

  • Find out the closest place in the code you wrote that causes the bug.

  • Determine what is wrong with the code.

    Usually the errors are low-level TeX errors caused by you using the command incorrectly.

Part 1. stacktrace

You must know how to determine where the error occur; in other words, get a "stack traceback".

TeX has mechanism for this purpose: What does \errorcontextlines do?.

Unfortunately, it has lots of limitations. See my answer there.

Not-directly-related note.

I'm working on something to improve the situation there.

  • some patch to the TeX engine to store where a token originally comes from
    (which would be harder because the internal representation of a token in the engine level, and the level in TeX programming is very, very different),
  • something that requires additional annotation to the code to mark explicit relax-points (or expandable-relax-points), plus something to print a traceback.

For now, while there's no such tool, my method (to determine where the error occur) consist of using \errorcontextlines to guess as good as possible where the error happens, then add debug-print statements at the (what I thought to be) relax-points to maximally localize the problematic code.

By relax-point I mean points such that any number of \relax can be added at that point such that they will be executed (not expanded except possibly the first one),
roughly speaking, they're the statement separation points.


⟨\relax point⟩
\int_compare:nNnT ⟨not \relax point⟩ {⟨not \relax point⟩ 12} < {34} {
    ⟨\relax point⟩
    \iow_term:x {⟨not \relax point⟩ 123}
    ⟨\relax point⟩
⟨\relax point⟩

By "add debug-print statements" I mean simply add e.g. \pretty:n {reached here} at the points you think to be relax points, and see which lines are printed.

\pretty:n {AAA}
\int_compare:nNnT {12} < {34} {
    \pretty:n {BBB}
    \iow_term:x {123}
    \pretty:n {CCC}
\pretty:n {DDD}

if the error goes like

! some error message...

then the error happens between BBB and CCC.

If, in the process of adding debug print statements, the error disappears, chances are

  • some command gobbles some following argument and it becomes the debug print statement instead of the following statement, or
  • some command looks ahead for e.g. optional space, and it terminates without expanding the following command when the pretty-print statement is seen. (for example, remember to add an explicit space ~ after a command that takes a number e.g. \catcode x=12, or a \relax after a TeX raw \numexpr etc. Using expl3's wrapper could avoid that, but for things like setting \catcode or such TeX registers, or for performance optimization, you'll have to stick with using raw TeX assignment.)

If they're replaced with \relax the error still not happen, then you could probably fix the issue by adding \relax there.

Although you'd also want to understand what went wrong at that point.
Just look carefully at the code for the points above.

After finding two consecutive relax-point such that you're sure the error happens between them, and the bug remains unfixed, continue with the following part.

Part 2. find the error

Make sure you read all of the TeXbook at least once.

The engine sometimes behave in counter-intuitive ways, and if you don't know that and "assume" how it behaves you'll be surprised quite a few times.

Example: Why is \unexpanded not respected after an \if? , Why does a newline character still disappear even with other/active catcode set? .

For comparison, Python has a few unexpected behavior as well https://stackoverflow.com/questions/3431676/creating-functions-in-a-loop nevertheless they're way fewer than that of TeX.

It's true that you won't be able to remember all of them; or sometimes "forget" some while debugging the program, in that case the following methods should help.

Notation: In the following paragraphs I use \pretty:n {⟨token list⟩}, \prettye:n {⟨token list⟩}, \pretty:V \⟨tl var⟩, \prettyshow:N ⟨control sequence⟩, etc. for a generic command to print out the content of a token list/meaning of a variable.
They're taken from my prettytok package; you can choose to use anything else such as \show, \showthe, \typeout, \showtokens, \tl_show:n, \msg_expandable_error:nn etc. (as the package author I recommend using my package, see the package documentation for more details.)

It's highly not recommended to step inside the command implementation, either with unravel package, or by printing the X-step-expansion.

Instead just assume it's a black box.


  • the implementation tends to be very, very complex and usually not the direct cause of the issue
  • more importantly, they tend to not have bugs and you want to debug your code, instead of their code.

Nevertheless, if the bug indeed is in the library instead of your code (see some example bugs of expl3 in https://github.com/latex3/latex3/issues?q=is%3Aissue), the instruction below should suffice to more-or-less create a MWE for reporting a bug to the library.

If you in addition want to directly find and fix the issue in the library, then

  • do not debug by patching the command, that would be somewhat equivalent to debugging a package by looking at its compiled binary code.
  • download the source dtx file, then modify that to add debugging commands etc..

actually, modifying sty file is not that bad, in the common case of docstrip. Only comment is removed and @@ get replaced with the package name. The structure is more or less preserved.

Note that

  • remember to make sure that the new sty/dtx file is actually used instead of the old one installed in your system.
    For example by temporarily adding \error to the local copy, compile the TeX file, then ensure that the error occurs.

Also, this is rare, but...

do not use the debugging tool to debug itself.

since, if you modify the base tool, it's not surprising that the debug printer might behave in unexpected ways.

For primitives like \immediate\write16 there is no issue, but otherwise be careful.

Print out the code that is going to be executed.

For example

\cs_new_protected:Npn \function #1 {
    \pretty:n {\tl_something:Nnn \tlvar {#1} {...}}  % ← add this.
    \tl_something:Nnn \tlvar {#1} {...}

This may seem redundant, but it might actually helps:

  • sometimes TeX tokenization rule might be different from what you expect. E.g.
    • ^^⟨content⟩ will become a different token
    • if you type \pretty:n { \abc ~ }, only \abc will be printed out without the explicit space.
  • besides, remember that TeX coding is not just "run this code with this variable value", it is self-modifying code.

If the command is "top-level" it tends to not produce any output, insert \pretty:V \resultvariable in the next relax-point to wherever it stores the result to. Otherwise you could e.g. \pretty:x {\tl_something:nnn {...} {...} {...}} to see the result.

Once you've determined exactly what is passed in the command (and optionally what the command results in, if it doesn't error out), read carefully the documentation of the command to see if it's supposed to do that, given that argument.

Chances are, you prepared the argument incorrectly, then the bug is in the code that prepares the argument.

You may also want to try calling that particular command, with that particular argument (*) in a separate independent document (**) to see if it errors out the same way.

If it doesn't, the issue is probably in some "global shared state" (e.g. value of escapechar, the definition of some particular macro etc.) so look into that.

It's rather unfortunate that there are many "global states" in TeX, although expl3 commands tend to not have this problem.

(*): if you have difficulty providing the argument because it has nonstandard catcode, I have the precattl package

(**): actually this is rather difficult if the argument depends on some other commands being defined. But it's the idea. Also then you may want to print out the value of the argument going to be passed to the argument to see if you typed it in correctly

Sometimes the documentation is not clear (refer to Where do I find out how a command/environment is defined? for where to find the documentation, but some command's documentation is not that clear.

Experiment around with the command (by making a MWE) to make sure that you understand it correctly.

Most notably are e-TeX commands, where etex_man.pdf is quite brief. If you get unexpected behavior involving the following commands, make sure you fully understand what the answer(s) in the linked question explain.

Sometimes expl3 command documentation is (was) not very clear, for example, that \tl_replace_all:Nnn only operates at top level. )

Talk about \scantokens, you should be able to implement an unexpandable \debugscantokens command that does similar thing to \scantokens, but writes the content to a temporary file, so you can more easily inspect the content of the temporary file to see if it is correct.

Although this does not necessarily behave in the same way e.g. even if newlinechar is not 10, a charcode-10 token will still create a physical newline in the generated file, but only for physical file. You should not rely on this behavior regardless.

For debugging expandable commands, or debugging input stream manipulation commands, my package has \prettye:n and \pretty[e]:w respectively, although you might choose to reimplement something similar yourself.

\pretty:w is destructive with respect to catcodes however, so that's a limitation. (somewhat like "can only be used once debug-printer")

Alternative way of debugging expandable commands consist of printing the X-step-expansion of the command.

One way is to use \exp_args:No \exp_args:NNo \pretty:o { ... } as mentioned above (note that this is not exactly identical to multiple-step expansion, as o-type expansion will convert notexpanded token to normal token; and if some unbalanced brace is generated it will have trouble.

There is the multiexpand package to simplify how this can be written. So \pretty:o {\romannumeral \multiexpand {5} ...}, or \expandafter \prettye:w \romannumeral \multiexpand {5} ... \prettystop for unbalanced-brace case.

For expl3 code, it would help to print out which input is actually passed to the command, if the argument specification is not the base form.

Unless the code uses l3flag's mechanism to keep a state (that is \csname ... \endcsname defines the control sequence to be \relax if it were previously undefined), it will be idempotent. As such if you have

\exp_args:NV \tl_something:oxV \a {\b \c} \d

then you can write

\exp_args:NV \pretty:oxV \a {\b \c} \d

to determine what is actually going to be passed to \tl_something function.

Comparing that to the "print the X-step-expansion" method mentioned above this has the advantages

  • you don't need to know exactly how many expansion steps the \exp_args:N* finishes
  • some \exp_args are unexpandable (x-type expansion etc.), so you'd need something like \unravel package; but then this one, for now, need step-by-step/interact with the console (to be fair unravel has u⟨text⟩ command that fast-forward to the bare command, but this might overshoot it on typo etc.)

If the issue is with "global state", the logical next step would be to print out the value of the global states right before the command is executed.

How can you know which global states are interesting? Most likely, those that you modify in your code. (you might forget to revert the value to a normal value)

An example of a "global state modification" bug. (my question)

Why do I get `! LaTeX Error: Missing \begin{document}. l.2 gdef @abspage@last{1}` starting from second compilation?

This one, I debugged it by...

  • first, figure out that the erroneous content happens in the aux file (e.g. with --file-line-error)

  • be aware that normally the string to be written to the aux file has the form \gdef \@abspage@last {1}. (reading LaTeX user manual won't help on this point. You can read source2e, or easier, experiment with compiling a simple working document)

  • in comparison to that, the \ are missing.


why is it so frustrating and requires knowing so many internal implementation details of (La)TeX?

In other languages e.g. Python/Lua, if you modify some commonly-used global variable built-in e.g. list or tonumber, it's not surprising if some mysterious error occur in an indeterminate number of lines later, possibly in some library.

(in this respect Lua is worse than Python, as its global namespace is truly global)

However, here, we don't have a facility/primitive to e.g. "get string representation of token list with escapechar=X", instead we must modify the global variable \escapechar to achieve the result.

Alternatively -- since escapechar is modified, it's possible to simply print out the value of escapechar at the end of the document and see it's -1, I think.

Another case. I accidentally set \let \FCDtabtomacro \⟨some typo that results in an undefined macro⟩, then call filecontentsdefmacro environment and gets "undefined control sequence" with unhelpful traceback.

The error context is not very useful here, but you could

  • try printing out the "global state" (in this case it should include definition of \FCDtabtomacro), since you modified it before invoking filecontentsdefmacro.
  • read the log file instead of the terminal, and find some way to visibly show the tab character e.g. :set list in vim (rather difficult, unless you know exactly what the error is already...)

Conclusion: You can debug it the same way how you debug other programming languages.

Remark: As someone who knows how to use gdb to debug C++, but does not know how to use pdb to debug Python, I'd say that fast compilation (or, interactive shell) & debug printing is a not-too-bad substitute for a true debugger.

For instance, instead of printing out the value in the debugger, you can simply add a line \pretty:nV {variable=} \⟨the variable⟩ right before the line it errors out, then read the last value printed out in the output.

Fast compilation/execution is needed, because otherwise you would need to wait a long time before each debugging step, which is frustrating in every language. (thus, as I mentioned above, if you don't have fast compilation, you need interactive shell or debugger)

Not that I'm saying a debugger is useless; in fact I already wrote one to do a few things that cannot be done otherwise (currently the debugger can

  • inspect the content of the "input stream", distinguishing between tokenized/untokenized content, while not touching the content itself -- this one is not done by unravel package or as far as I know any existing way without modifying the compiler, and
  • do a one-step execution (standard functionality)

however it needs about 10x more functionality to be anywhere comparably as useful as pretty-print-debugging.)

and I'm pretty comfortable with using pretty-print-debugging for now so it's not going to happen any time soon.

Miscellaneous notes

Some packages (expl3 for example) has debug mode, try reading the documentation and enable it (if any)

To me, it doesn't seem very useful, however.

Depends on the configuration, it might be tedious to repeatedly stop/start the compiler and/or search for the error message in the log file/verbose terminal output.

Refer to compiling - Reducing the console output of LaTeX - TeX - LaTeX Stack Exchange for ways to make the process easier.

Avoid linebreaks in LaTeX console / log output (or increase columns in terminal) might help a bit regarding viewing the error context, but only if the place you view the error context does not do line wrapping (for me, reading it in the log file is better than reading in the terminal, both because of no line wrapping and that trailing spaces are preserved -- nevertheless make sure that you read all the lines, as some might be indented a lot)

Sometimes, you have your package command producing spurious letters that is being typesetted.

Usually, this is caused by either...

debugging this one is rather difficult, but one way I find reasonably good is to put the whole code inside the preamble, so LaTeX has the necessarily mechanism (everypar hook) to give an error when something is typeset, so you can quickly locate where the error is.

If everything fails, you can still do binary debugging.

Compare to a Python program with many lines, each line might or might not set the value of a global variable to something undesired, that would be hard to track down. (with a debugger you can set a memory watchpoint on the variable, but in TeX there's no debugger for now)

unravel package

Step-by-step emulation of the TeX engine.

As mentioned, it does not support \directlua, other XeTeX/LuaTeX-specific things (new font handling etc.), and catcode-changing commands.

Reference: macros - A LaTeX log analyzer application (visualizing TeX expansion) - TeX - LaTeX Stack Exchange

(despite the title, the answer there announces the package unravel. TeX log file is just too hard to parse, as all the catcode information is dropped -- besides, if \escapechar=-1, then the backslash in output is also dropped completely)

Personally I don't find this much more useful than printf-debugging yet.

Besides the TeX macro \tracingall, there's also the package trace which claims to make the output nicer.

See also: debugging - Latex \tracing commands list? - TeX - LaTeX Stack Exchange

Personally I don't have much experience using the trace to debug, except in some particular cases e.g. my answer that explains how to find the line number that runaway argument starts.

This one is from an user perspective.

No error, but weird output

This one is harder to track down than that if there's an actual error, as it's difficult to pinpoint where exactly in the code does the bug happen.

First, read the documentation carefully to be sure that what you want is supported by the package. In almost all cases, the behavior is documented in the manual. (otherwise it would be a bug.)

How can I know where in the manual should I read?

  • Make a minimal example. For example, if the code currently uses component A, B and C, and if you replace B and C with dummy text the problem persists, then you only need to (re-)read the document of component A.

For an example.

How do you balance two frame boxes in the same page?

As you can tell... in this case the behavior is documented in the documentation, but it's not very practical to read through the whole LaTeX manual to find the behavior.

Another example.

Last time I use a \int_step_inline inside a tabular environment, and it creates a spurious cell (see Pipe input for table content is working odd for an example.)

This was a difficult one to debug (I end up asking in TeX.SE chat), but I think it could be debugged by...

  • first, make a MWE. Should not be difficult, and you end up with only a tabular environment and a \int_step_inline.

  • replace the LaTeX command with the TeX primitive.

In particular, tabular environment with halign, and int_step_inline with some loop you implement yourself (normally this would be easier than trying to debug the expl3 implementation, even though expl3 is fully documented)

If the bug disappear when you replace something, you know that the thing you just replaced either has a bug.

Anyway it's not supposed to be used that way, so a fix would be to do something that is guaranteed to work (in this case it would be build the full token list with int_step_inline before running tabular environment)

Otherwise, it's because the LaTeX tool inherit some quirk from the TeX primitive --
which is the case here, where halign fully expands the first argument until it sees something unexpandable, and if it's not egroup then it starts a new cell.

A case of package bug.

Square brackets in listings caption

(as far as I can see this is a package bug, as the unusual behavior with [] is not written in the manual)

In this case you should be able to quickly find out where the error is, make a MWE, without delving into the implementation details of the package.

In practice, you'd want to know how to fix the problem as well. (without having to contact the package author and wait for a response)

To come up with a fix is not very easy, but if you know a bit about how LaTeX is usually implemented, the [ is accidentally taken as the delimiter of an optional argument instead of some text to be typeset, you can prepend \relax to disable that.

Sometimes your program is correct, but it's very slow. Another topic.


My opinion is that debugging TeX is harder than debugging Python/C++, but not by much;
and, with better debugging tools, the same programming tasks will take the same effort to debug;
nevertheless, the tasks you do in TeX tends to be more complicated than that in Python (it's uncommon to do typesetting in typical programming languages).

For OP's example on \textsf / \enquote, my opinion is that if Python has similar functionality the error would be similarly misleading
(as, the computer first collect all the text and which font they use; then apply microtype adjustment on it. The error happens way after the text is first collected, thus the line number is misleading.) --
unless the author intentionally add code to record the stacktrace where the code is collected.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .