Choosing a TeX Engine: pdfLaTeX, XeLaTeX or LuaLaTeX for a New Project
Pick a TeX engine by the constraint that binds: Unicode input, a required system font, or compile-time scripting. Includes an iftex-guarded preamble and a log-based verification check.
16 Jun 2026, 12:03 UTC

Engine choice is usually decided by one of three constraints, in this order: the document must accept Unicode input beyond Latin-1; it must use a specific installed typeface; or the build needs programmatic logic at compile time. If none of those apply, pdfLaTeX is the low-risk default, because it is what most journal classes and templates assume. If a required typeface is the binding constraint, you are choosing between XeLaTeX and LuaLaTeX, and the tiebreaker is scripting.
What the three engines actually differ on
Everything structural — \input, \include, biblatex with biber, hyperref — behaves the same across all three. The decision is mostly about the preamble, not about how you organise files.
| Engine | Input | Fonts | Scripting | Speed profile | Main risk |
|---|---|---|---|---|---|
| pdfLaTeX | 8-bit encoded input; UTF-8 works through the kernel's default input encoding, but only characters present in the loaded font encoding render | TeX font packages (Type 1, T1/OT1); no access to operating-system fonts | None | Fastest startup; usually fastest overall | Cannot load a brand or institutional typeface; fontspec is unusable |
| XeLaTeX | UTF-8 natively | OpenType and TrueType system fonts via fontspec | None | Moderate startup | No microtype font expansion, so line breaking differs from the other engines |
| LuaLaTeX | UTF-8 natively | OpenType and TrueType system fonts via fontspec | Lua, via \directlua and the luacode environment | Higher startup cost | Slower builds; some packages lag or need wrappers |
Trade-offs worth naming before you commit
fontspec is a one-way door for the preamble
fontspec requires XeTeX or LuaLaTeX. A source tree whose preamble calls \setmainfont cannot be compiled by pdfLaTeX without replacing the font setup entirely — there is no shim. If a journal later insists on pdfLaTeX, you are rewriting font selection, not adding a build flag.
Identical source does not mean identical output
microtype's feature coverage differs by engine. Font expansion is available under pdfLaTeX and LuaLaTeX but not XeTeX, so the same source can produce different line breaking and a different page count depending on the engine. For a document with a strict length limit, that is the real cost of switching late.
Package support is uneven
Some packages are engine-specific or need wrappers. Test the complete preamble — every package you intend to load — with the candidate engine before committing to it. A minimal example will not surface the interactions that break a real document.
Startup cost versus scripting
LuaLaTeX's Lua layer is the reason to choose it and also the reason it starts slower. If you are not actually calling Lua, you are paying that cost for nothing. XeLaTeX sits between the two: Unicode and system fonts without the scripting layer.
One source tree, guarded with iftex
If you need to support more than one engine — your own drafts building with a system font while a journal builds with pdfLaTeX — the iftex package exposes \ifPDFTeX, \ifXeTeX and \ifLuaTeX so a single preamble can branch. Run the following in a scratch directory; no elevated permissions are needed beyond write access to that directory. Replace the font family with one actually installed on your system.
\documentclass{article}
\usepackage{iftex}
\ifPDFTeX
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\else
\usepackage{fontspec}
\setmainfont{TeX Gyre Termes}% replace with an installed family
\ifLuaTeX
\usepackage{microtype}
\fi
\fi
\typeout{ENGINE-CHECK: \ifPDFTeX pdfLaTeX\fi\ifXeTeX XeLaTeX\fi\ifLuaTeX LuaLaTeX\fi}
\begin{document}
Engine check: \ifPDFTeX pdfLaTeX\fi\ifXeTeX XeLaTeX\fi\ifLuaTeX LuaLaTeX\fi.
\end{document}
Note on versions: since the 2018 LaTeX kernel, UTF-8 is the default input encoding, so \usepackage[utf8]{inputenc} is not required for UTF-8 input under pdfLaTeX. That does not give pdfLaTeX the same reach as the other engines — characters outside the loaded font encoding still will not render.
Verify which engine and fonts you actually got
- Compile the file once per engine from the scratch directory:
pdflatex -interaction=nonstopmode minimal.tex, thenxelatex -interaction=nonstopmode minimal.tex, thenlualatex -interaction=nonstopmode minimal.tex. Use separate output directories if you want to keep the logs side by side. - Open each
.logand read its first line. It names the engine and its version — look forpdfTeX,XeTeXorLuaTeXthere. This is the authoritative answer to "which engine ran", not the template's documentation. - Search the log for the
ENGINE-CHECKline printed by\typeout. If it reports pdfLaTeX when you invoked xelatex, your guards are in the wrong order or the conditionals are malformed. - Add
\listfilesto the preamble and rebuild. The log then lists every package file and version used in the build. Record that list; it is what you need to reproduce the build on another machine or a hosted service. - Inspect the produced PDF's embedded fonts with a font-listing utility —
pdffontsfrom poppler-utils is one. You are looking for the family you named in\setmainfont. If you see Computer Modern Type 1 fonts instead, thefontspecbranch did not run. - Rebuild the real document, not the minimal test. Package interactions appear only in the full preamble.
If you switch engines mid-project, keep the previous PDF and log so you can compare page counts and line breaking. Reverting means restoring the earlier preamble block and rebuilding — the source content itself does not change.
Limitations and what this does not settle
TeX distributions bundle different package versions, and hosted build services may pin versions you cannot see. Behaviour observed on one installation may not transfer to another. A template's documentation naming an engine is not authoritative either; confirm what the build actually invokes by reading the build script, Makefile, latexmkrc or the service's configuration. If you cannot inspect the build, treat the engine as unknown and test it.
This guide covers LaTeX engines only; it does not address ConTeXt or plain TeX, and it does not attempt to characterise every engine-specific output difference. Where a package's engine support is unclear, check its documentation against your installed version rather than assuming parity.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.