Given a control sequence \z that was defined like \let\z=z, how do you get the catcode the character z had at the time \z was defined?

The obvious solution

{\catcode`z=12 \global\let\z=z}

seems to work at the first glance, but it gives the wrong result 11 because '\z isn't interpreted as the previously defined control sequence but just another denotation of the character code of the letter z.

Using a different control sequence name makes it obvious

{\catcode`z=12 \global\let\thez=z}

when it fails with

! Improper alphabetic constant.

This question deals with a similar problem, getting the character code of such a control sequence, but I don't see a way to use it for this problem as \meaning only returns characters of category 12 and 10.

  • (In expl3 use \token_case_catcode.)
    – user202729
    Dec 16, 2021 at 14:54

2 Answers 2


An implicit character cannot be active, nor it can have catcode 0, 5, 9, 14 or 15. Check each possibility:

  \ifcat\noexpand#1\bgroup 1\else
  \ifcat\noexpand#1\egroup 2\else
  \ifcat\noexpand#1 10\else

\let\one={ \let\two=} \let\three=$
\let\four=& \let\six=# \let\seven=^
\let\eight=_ \let\eleven=z \let\twelve=0
\begingroup\def\\#1:{\global\let\ten= #1}\\ :\endgroup












\ifnum\catcodeofimplicitchar\one=1 GOOD\else BAD\fi


enter image description here

  • I chose this answer because it's more direct and easier to understand
    – siracusa
    Sep 28, 2016 at 15:45

You do want the same general idea as in How to get character code defined with \let (! Improper alphabetic constant), but you need to use the text of the \meaning. An example which covers three cases (space, letter, other), and which extends naturally to the other cases:

  \noexpand\getcatcodespace#1{}{}\detokenize{blank space}\noexpand\stop
  \noexpand\getcatcodeletter#1{}{}\detokenize{the letter}\noexpand\stop
  \noexpand\getcatcodeother#1{}{}\detokenize{the character}\noexpand\stop
  \def\noexpand\getcatcodespace##1\detokenize{blank space}##2\noexpand\stop{%
  \def\noexpand\getcatcodeletter##1\detokenize{the letter}##2\noexpand\stop{%
  \def\noexpand\getcatcodeother##1\detokenize{the character}##2\noexpand\stop{%

This is all expandable so it can go into the argument of \catcode or similar. I've not added any defensive code in case \z here is a macro which might contain the test string, a chardef, etc., but one can extend the idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.