The table of contents printed on page one of your PDF was not written by the run that produced that PDF. \tableofcontents, \listoffigures and \listoftables each read a small file left behind by the previous LaTeX compilation — .toc, .lof and .lot — and those files are not caches of text but short programs, one line per entry, which the next run executes. Grasp that single fact and nearly everything baffling about these three commands falls out of it: why a fresh document shows nothing, why the page numbers are off the first time, why a footnote inside a section title explodes on the second compilation rather than the first, and why LaTeX never once warns you that the contents on the page are wrong.
What is actually inside a .toc, .lof or .lot file
One command per entry, all of them \contentsline. The three lists share one mechanism: \tableofcontents owns the .toc, \listoffigures the .lof, \listoftables the .lot, and every one of those files carries the name of your root file. \contentsline takes four arguments — the kind of entry, the text to print, the page number, and a link target. The fourth is empty in plain LaTeX; load hyperref and it fills with a PDF destination such as section.1.1. A .toc is therefore not a draft of your contents but a list of instructions handed to the next run.
% one \contentsline per entry: unit, text, page, link target
\contentsline {chapter}{\numberline {1}Body}{5}{chapter.1}%
\contentsline {section}{\numberline {1.1}Short form}{5}{section.1.1}%
% and in mydoc.lof, written by \caption inside a figure:
\addvspace {10\p@ }
\contentsline {figure}{\numberline {1.1}{\ignorespaces Short caption}}{5}{figure.1.1}%Those lines, however, are not written to the contents file directly. They accumulate first in the .aux file as \@writefile{toc}{...}, and only when LaTeX closes and re-reads the .aux at \end{document} do they flow into the .toc. The detour has two practical consequences. First, the write stream is opened by \tableofcontents itself, so if that command appears nowhere in the document, no .toc file is produced at all — the entries simply stay in the .aux. Second, because the writing happens in one burst at the very end, \tableofcontents may sit anywhere. Put it on the last page and you still get a complete table of contents listing every heading, including the ones above it.
| Command | File it writes | Where the entries come from |
|---|---|---|
\tableofcontents | .toc | headings from \chapter down to \subparagraph, plus \addcontentsline{toc}{...} |
\listoffigures | .lof | \caption inside a figure — the short optional argument if you gave one |
\listoftables | .lot | \caption inside a table — mechanically identical to the .lof |
\addcontentsline | the extension you name | one hand-written line; the page number is \thepage at that moment |
\addtocontents | the extension you name | material rather than an entry — spacing, formatting commands |
Why the contents come out empty — and why LaTeX never warns you
Because on the first run there is no .toc to read. The log shows the single line No file mydoc.toc., and \tableofcontents typesets its heading and moves on. The file is written at the end of that run, so the entries first reach paper on the second. And on the second run the contents themselves start occupying pages, which shifts every page number after them — sometimes a third run is needed before things settle. One run stores the information, another retrieves it; this is exactly the two-stage arrangement behind \label and \ref, and the cross-references page covers that machinery in detail.
And here is the half that rarely gets mentioned: LaTeX never warns you about this lag. An undefined reference produces LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right., but only because labels are compared against their previous values in the .aux. The contents get no such comparison. When the table printed on the page disagrees with the .toc just written, the log says nothing at all — inspect the log of a run that emitted a blank table of contents and you will not find a single warning. That is the real argument for letting a build tool such as latexmk count the passes instead of counting them yourself: it repeats until the .toc stops changing.
The same silence turns up in a nastier form. Switch a manuscript from report to article and the old .toc still holds lines of the form \contentsline {chapter}{...}. article defines no \l@chapter, and since \contentsline merely calls \csname l@chapter\endcsname, an undefined name quietly becomes \relax — so the title and the page number are dumped into the contents as ordinary body text. No error, no warning, just a mysterious line reading something like “1 Alpha2”. Whenever the contents look corrupted after a change of class or of directory layout, the shortest route back is to delete the .toc (and the .aux) and rebuild.
There is only one tocdepth — the setting that empties your list of figures
tocdepth is a counter naming the deepest level to be printed in the contents: \setcounter{tocdepth}{1} stops at sections, {2} reaches subsections. The defaults are 3 in article and 2 in book and report. But it is not a counter for the contents alone. Look inside article.cls and book.cls and \l@figure is \@dottedtocline{1}{1.5em}{2.3em} — every entry in the list of figures is set at level 1, and \l@table is an alias for it. And what \@dottedtocline compares against is the one and only tocdepth, shared by all three lists.
The consequence is unkind. In a book, deciding that the contents should list chapters only and writing \setcounter{tocdepth}{0} empties the list of figures and the list of tables. The .lof still holds every entry, but level 1 exceeds a tocdepth of 0, so not a single line is printed. No error is raised. The fix is small: wrap \listoffigures in a group and raise tocdepth inside it. That tocdepth filters as the file is read back — which is why changing the depth never forces the .toc to be regenerated and costs just one extra run — is covered on the document-structure page.
\setcounter{tocdepth}{0} % contents: chapters only
% ... but this alone would print an EMPTY list of figures.
% Raise the depth for the float lists only:
\begingroup
\setcounter{tocdepth}{1}
\listoffigures
\listoftables
\endgroup
% Because the .toc is a program, a depth change can also be
% injected into the middle of it, taking effect from here on:
\addtocontents{toc}{\protect\setcounter{tocdepth}{1}}The short title is the one that gets recorded: \section[...]
The short one, in square brackets, is what enters the .toc; the long one, in braces, appears only in the body. Write \section[Short form]{A long title that sprawls across the page} and the heading stays long on the page while the contents and the running head take the short form. \caption[Short caption]{A long explanation} follows the same rule, and it is the short version that reaches the .lof and .lot — the captions page covers that side in detail. What matters here is that this optional argument is not a luxury for tidying up appearances.
A heading is written out to the .toc — poured into a file and read back on the next run. So put a fragile command inside one, as in \section{Title with a note\footnote{note}}, and the first compilation passes without complaint while the second collapses at the moment the file is read back. Runaway argument?, then ! Paragraph ended before \contentsline was complete., then ! Argument of \@sect has an extra }. — messages that look unrelated to any heading, though the culprit is the footnote sitting in the .toc written a moment ago. The error arrives one run late for exactly the reason the contents do. The prescription is the optional argument: \section[Title with a note]{Title with a note\footnote{note}} keeps the footnote out of the .toc, and it never breaks again.
% the bracketed form is what lands in .toc, .lof and the running head
\section[Short form]{A long section title that would wrap in the contents}
% fragile material belongs in the braces only, never in the file
\section[Title with a note]{Title with a note\footnote{note text}}
\begin{figure}
\includegraphics{plot}
\caption[Short caption]{A long caption explaining every detail}
\end{figure}The same fact — that a heading is used in three places — resurfaces in another guise once hyperref is loaded. The title is reused for the PDF bookmarks, and a bookmark is pure text, so it admits no mathematics. Write \section{Properties of $\mathcal{A}$} and you get Package hyperref Warning: Token not allowed in a PDF string (Unicode) while the formula is silently dropped. The escape hatch is \texorpdfstring{$\mathcal{A}$}{A}, which hands one version to the typesetter and another to the string, and which the hyperref page covers.
Getting a starred heading into the contents: addcontentsline and where to put it
Put \addcontentsline{toc}{section}{Introduction} on the line immediately after the heading. A \section* or \chapter* carries no number and writes nothing to the .toc, so if you want it listed you have to inject the line yourself. All three arguments are required.
ext— the extension of the target auxiliary file:tocfor the contents,loffor the figures,lotfor the tables.unit— the kind of entry. Fortocit ispart,chapter,section,subsectionand so on, and that level’s formatting and indent are used; forlofit isfigure, forlottable.text— the string to list. Prefix\protect\numberline{}to align the title with numbered entries, and put\protectin front of any fragile command.
Placement decides the outcome. Open the definition in latex.ltx and \addcontentsline merely writes out \contentsline{unit}{text}{\thepage}{} — it burns in the page number current at the instant that line executes. Since \chapter* starts a fresh page, putting the line carelessly before the \chapter* records the previous page. The experiment is blunt: an entry placed before pointed at page 2, the one placed immediately after at page 3. Your reader turns to the page and finds no chapter there. LaTeX supplies the page number itself, so you never write one into text.
% right: the line runs after the page break that \chapter* causes
\chapter*{Acknowledgements}
\addcontentsline{toc}{chapter}{Acknowledgements}
\section*{Introduction}
\addcontentsline{toc}{section}{Introduction}
% \addtocontents injects material, not an entry
\addtocontents{lof}{\protect\vspace{2ex}}Its companion, \addtocontents{ext}{text}, injects material rather than a line. It takes only the target extension and the content to write, and no page number is attached. Peer into a .lof and you will find a line reading \addvspace {10\p@ } — LaTeX itself injects spacing by exactly this route whenever a chapter changes. In short: a line with a page number goes through \addcontentsline; spacing and formatting go through \addtocontents. Both write for the benefit of the next run, so a fragile command such as \vspace needs \protect.
Listing the lists themselves: tocbibind
One line, \usepackage{tocbibind}, and the list of figures, the list of tables, the bibliography and the index all appear in the contents by themselves. Those headings carry no number — \section* in article, \chapter* in book and report — so left alone they never show up. You can line up \addcontentsline calls by hand instead, but for a bibliography or an index that runs over several pages the placement is easy to get wrong, and the package is the safer bet.
By default it also lists the contents inside the contents, which is why nottoc is the option most people go looking for first. The exclusions are nottoc, notlof, notlot, notbib and notindex. Going the other way, numbib and numindex typeset the bibliography and the index as numbered chapters or sections instead of unnumbered headings. The tocbibind shipping in TeX Live 2024 is v1.5k from 2010, by Peter Wilson — the same author as tocloft.
% list everything except the contents itself
\usepackage[nottoc]{tocbibind}
% the manual equivalent, one line per list
\cleardoublepage
\addcontentsline{toc}{chapter}{\listfigurename}
\listoffiguresReshaping indents, fonts and dotted leaders with tocloft
Load \usepackage{tocloft} and every level gets its own indent, number width, font and dotted leader. The command names are systematic: a prefix for the level (toc for part, chap for chapter, sec for section, subsec for subsection, fig for figures, tab for tables) combined with a role. Indent and number width are set together, as in \cftsetindents{section}{1.5em}{2.5em}; when the numbers grow wide enough to collide with the title, widen the third argument. Fonts are separate for the entry title (\cftsecfont) and its page number (\cftsecpagefont).
The dotted leader hides a trick the names do not reveal. Dot spacing is the length \cftdotsep (default 4.5) — smaller packs the dots together, larger spreads them out. And \cftnodots, the thing you use to remove a leader, is not a flag: inside tocloft.sty it is simply the number 5000, a separation so wide that no dot fits on the line. The same trick explains a detail you have seen a thousand times without noticing: \cftpartdotsep and \cftchapdotsep default to \cftnodots, which is why in a standard table of contents only the part and chapter lines have no dots.
| Command | What it controls | How it is set |
|---|---|---|
\cftsetindents | A level’s indent and number width | \cftsetindents{section}{1.5em}{2.5em} |
\cftsecfont | Font of a section entry’s title | \renewcommand{\cftsecfont}{\bfseries} |
\cftsecpagefont | Font of a section entry’s page number | for chapters, \cftchappagefont |
\cftsecleader | Dotted leader for a section entry | replace the \cftdotfill{\cftdotsep} in it |
\cftdotsep | Dot spacing; default 4.5, smaller is denser | \renewcommand{\cftdotsep}{2} |
\cftnodots | The number 5000 — a spacing no dot fits into | use it to remove a leader entirely |
\cftloftitlefont | Font of the list-of-figures heading itself | for the contents, \cfttoctitlefont |
\usepackage{tocloft}
\renewcommand{\cftsecfont}{\bfseries}
\renewcommand{\cftsecpagefont}{\bfseries}
\renewcommand{\cftsecleader}{\bfseries\cftdotfill{\cftdotsep}}
\renewcommand{\cftdotsep}{2} % tighter dots
\cftsetindents{section}{1.5em}{2.5em} % indent, number width
% drop the leader on section lines altogether
\renewcommand{\cftsecleader}{\cftdotfill{\cftnodots}}When tocloft runs out: titletoc and etoc
Where tocloft adjusts the measurements and fonts of the existing lines, titletoc and etoc rewrite the structure of a line. The heart of titletoc (by Javier Bezos, in the same bundle as titlesec) is \titlecontents, which defines, level by level, the material before a line, how the number is set, the title, the filler that runs to the page number, and what follows the line. When plain dots are all you need there is a shorthand, \dottedcontents. Beyond that, \startcontents, \printcontents, \stopcontents and \resumecontents let you drop a partial table of contents for one chapter at the head of that chapter. In a document whose headings are already shaped by titlesec, the contents can be shaped in the same idiom.
etoc (by Jean-François Burnol) goes further, redesigning the contents wholesale through a two-layer framework of “line styles” and “global styles.” Its centrepiece is \localtableofcontents, which extracts a per-chapter partial contents from the same .toc as often as you like; at this level even a tree-shaped table of contents comes within reach. As a decision procedure, three steps work well in practice: fix measurements and fonts with tocloft; move to titletoc when you need to change how a line is assembled; go to etoc when you want to take over the design of the contents itself.