What is the best way to check if an argument of a control sequence is a control sequence?

If you mean that control sequence is something what begins with backslash (irrelevant what meaning it has) than you can try following code. Note that this code checks all alternatives (including spaces, more tokens in the argument or {}).

{\escapechar=-1\xdef\nb{\string\\}} % normal backslash
\def\IsArgaCs#1{\futurelet\next\isargacsA#1\end}
\def\isargacsA{\def\nextB{\expandafter\isargacsB\string}%
   \expandafter\ifx\space\next \def\nextB{\isargacsB.}\fi
   \ifx\next\bgroup \def\nextB{\expandafter\isargacsC\string}\fi
   \ifx\next\end \def\nextB{\isargacsB.}\fi
   \nextB
}
\def\isargacsB#1#2\end{\if\nb#1TRUE\else FALSE\fi}
\def\isargacsC#1{%
   \if\nb#1\def\nextB{\isargacsB\nb}\else
      \def\nextB{\expandafter\isargacsB\expandafter.\expandafter}%
   \fi
   \nextB{\iffalse}\fi
}

1)\IsArgaCs{\a}
2)\IsArgaCs{a}
3)\IsArgaCs{\b\c\d}
4)\IsArgaCs{\c}
\def\name{A}
\def\i{1}
5)\IsArgaCs{\name\i}
\let\d=z 
6)\IsArgaCs{\d}
7)\IsArgaCs{~}
8)\IsArgaCs{ }
9)\IsArgaCs{{}}

Edit: I've improved the test of \bgroup because \bgroup must return TRUE but real open brace must return FALSE. The trick with relatively balanced braces must be used.


Are you really sure you wish to check for control sequence tokens?

Primitives like \relax are not macros but they are control sequences.

Active characters (character tokens of category code 13) are not control sequence tokens but they can be defined to be macros.

A control sequence token or an active character token whose meaning denotes that it is undefined also is not a macro.

The case of the argument in question being empty also is a case where no macro/no control sequence token is present.

Arguments starting with opening curly braces /catcode-1-characters might require special attention.

The following code provides testing by checking the meaning of the first token of the argument in question in case that argument is not empty.

Leading space tokens and leading catcode-1-characters are taken into account.

%%===============================================================
%% \firstoftwo and \secondoftwo:
%%...............................................................
\long\def\firstoftwo#1#2{#1} \long\def\secondoftwo#1#2{#2}
%%---------------------------------------------------------------
%% \AtIfArgsFirstTokenIsMacro - takes three macro-arguments and
%%              forks depending on whether the first argument
%%              holds a first/leading token whose \meaning equals
%%              one of the phrases "macro:", "\long macro:".
%%              If so delivers the second argument, otherwise
%%              delivers the third argument.
%%
%%              The macro is suitable for expansion contexts
%%              and due to \romannumeral-expansion delivers the
%%              result after two expansion steps/after "being
%%              hit by two \expandafter-chains".
%%
%%              The case of the first argument being empty/not
%%              holding any token at all is considered a case
%%              where the argument does not hold a first/leading
%%              token at all and thus does not hold a first/leading
%%              token whose meaning equals one of the phrases
%%              "macro:", "\long macro:".
%%
%%              The test does not rely on some token being
%%              undefined. eTeX- or whatsoever extensions are not
%%              required.
%%
%%              I assume there is room for improvement/shortening
%%              the code.
%%
%%              Testing by means of macros for a leading
%%              \outer-macro-token in the argument is somehat
%%              obsolete and impossible as \outer-tokens cannot
%%              occur inside macro-arguments.
%%
%%...............................................................
\def\AtIfArgsFirstTokenIsMacro#1#2{%
  \long\def\AtIfArgsFirstTokenIsMacro##1##2##3{%
    \romannumeral\iffalse{\fi\expandafter\secondoftwo\expandafter
    {\expandafter{\string##1}\expandafter\expandafter\expandafter
    \firstoftwo\expandafter\expandafter\expandafter\firstoftwo
    \expandafter\secondoftwo\expandafter{\expandafter{\iffalse}}\fi
    \expandafter\secondoftwo\string}\expandafter\firstoftwo
    \expandafter{\iffalse}\fi\iffalse{\fi
    \expandafter\innerAtIfArgsFirstTokenIsMacro\meaning##1#1}{}%
      {%
        \iffalse{\fi
        \expandafter\innerAtIfArgsFirstTokenIsLong\meaning##1#2 #1}{}%
      }{\secondoftwo}%
    }{\firstoftwo}%
    {0 ##3}{0 ##2}%
  }%
  \long\def\innerAtIfArgsFirstTokenIsMacro##1#1{%
    \innerAtIfArgsFirstTokenIs{##1}%
  }%
  \long\def\innerAtIfArgsFirstTokenIsLong##1#2 #1{%
    \innerAtIfArgsFirstTokenIs{##1}%
  }%
  \long\def\innerAtIfArgsFirstTokenIs##1{%
    \iffalse{\fi\expandafter\secondoftwo\expandafter{\string##1}%
    \expandafter\firstoftwo\expandafter{\iffalse}\fi\expandafter
    \expandafter\expandafter\firstoftwo}{\expandafter\expandafter
    \expandafter\secondoftwo}\expandafter\secondoftwo\expandafter
    {\iffalse}\fi
  }%
}%
% Somehow get the catcode-12-token-phrases "macro:" and "\long"
% as argument of \AtIfArgsFirstTokenIsMacro - hereby
% it is relied on the usual catcode settings and on primitives not
% being redefined and on integer parameters like \globaldefs and
% \endlinechar and \escapechar holding usual values, thus:
\begingroup
\edef\AtIfArgsFirstTokenIsMacro{%
  {\string m\string a\string c\string r\string o%
  \string :}{\string\long}%
}%
\expandafter\endgroup%
\expandafter\AtIfArgsFirstTokenIsMacro%
\AtIfArgsFirstTokenIsMacro%
%%===============================================================

\def\test{defined}

\long\def\testb{defined}

\catcode`\Z=13

1 \AtIfArgsFirstTokenIsMacro{\UndFINeD}{Macro}{Not Macro} % OK

2 \AtIfArgsFirstTokenIsMacro{\undefined A}{Macro}{Not Macro} % OK

3 \AtIfArgsFirstTokenIsMacro{\undefined A\fi}{Macro}{Not Macro} % OK

4 \AtIfArgsFirstTokenIsMacro{}{Macro}{Not Macro}  % OK

5 \AtIfArgsFirstTokenIsMacro{Z}{Macro}{Not Macro} % OK

6 \AtIfArgsFirstTokenIsMacro{\relax}{Macro}{Not Macro} % OK

7 \AtIfArgsFirstTokenIsMacro{\TeX}{Macro}{Not Macro} % OK

8 \AtIfArgsFirstTokenIsMacro{\test}{Macro}{Not Macro} % OK

9 \AtIfArgsFirstTokenIsMacro{\par}{Macro}{Not Macro} % Evaluate/OK

10 \AtIfArgsFirstTokenIsMacro{\fi}{Macro}{Not Macro} % OK

11 \AtIfArgsFirstTokenIsMacro{\else}{Macro}{Not Macro} % OK

12 \AtIfArgsFirstTokenIsMacro{\if}{Macro}{Not Macro} % OK

13 \AtIfArgsFirstTokenIsMacro{#}{Macro}{Not Macro} % OK

14 \AtIfArgsFirstTokenIsMacro{ }{Macro}{Not Macro} % OK

15 \AtIfArgsFirstTokenIsMacro{\endcsname}{Macro}{Not Macro} % OK

16 \AtIfArgsFirstTokenIsMacro{\AtIfArgsFirstTokenIsMacro}{Macro}{Not Macro} %Long Macro

17 \AtIfArgsFirstTokenIsMacro{\bgroup}{Macro}{Not Macro}% OK

18 \AtIfArgsFirstTokenIsMacro{\egroup}{Macro}{Not Macro}% OK

\letZ=\relax

19 \AtIfArgsFirstTokenIsMacro{Z}{Macro}{Not Macro}% OK

20 \AtIfArgsFirstTokenIsMacro{\testb}{Macro}{Not Macro}% OK

21 \AtIfArgsFirstTokenIsMacro{\testb bla bla}{Macro}{Not Macro}% OK

22 \AtIfArgsFirstTokenIsMacro{bla \fi}{Macro}{Not Macro}% OK

23 \AtIfArgsFirstTokenIsMacro{several tokens}{Macro}{Not Macro}% OK

24 \AtIfArgsFirstTokenIsMacro{A}{Macro}{Not Macro}% OK

25 \AtIfArgsFirstTokenIsMacro{{\undefined A}\fi}{Macro}{Not Macro}% OK

26 \csname\AtIfArgsFirstTokenIsMacro{\tEx}{tEx}{TeX}\endcsname% OK

27 \if0\AtIfArgsFirstTokenIsMacro{\tEx}{0}{1}\tEx \else \TeX\fi% OK

\bye

The tokcycle package has reasonably extensive ability to glean the characteristics of a token, as shown in this MWE. In lines 1.-7., I show some of these abilities. In lines 8a. and 8b., I propose how to find a macro, which I define here as an expandable control sequence.

\documentclass{article}
\usepackage{tokcycle}
\newcommand\characteristics{%
  \ifactivetokunexpandable Unexpandable-Active-Token \else
    \ifactivetok Active-Token \fi\fi
  \ifactivechar Active-Char-Code \fi
  \ifimplicittok Implicit \fi
  \ifcatSIX Cat-6 \fi
}
\newcommand\IsArgaCs[1]{%
  \tokencycle{\characteristics}{}{Control Sequence \characteristics}%
  {Space \characteristics}#1\endtokencycle}
\parindent 0pt
\begin{document}
1: \IsArgaCs a\par
2: \IsArgaCs\relax\par
\let\littlet t
3: \IsArgaCs \littlet\par
\def\tmp{Q}
\catcode`Q=\active
\def Q{X}
4a: \IsArgaCs Q\par
\let QX
4b: \IsArgaCs Q\par
4c: \expandafter\IsArgaCs\tmp\par
\catcode`Q=11 
4d: \IsArgaCs Q\par
5a. \IsArgaCs #\par
\let\svhash#
5b. \IsArgaCs\svhash\par
6. \IsArgaCs\space\par
\makeatletter
7. \IsArgaCs\@sptoken\par
\smallskip
One can determine macro-ness by letting active character to the test token
  and testing the active character.  If not a macro, it comes up as 
  unexpandable:\\
\catcode`Q=\active
\let Q\relax
8A: \IsArgaCs Q\par
\let Q\today
8B: \IsArgaCs Q\par
\end{document}

enter image description here