% js-types-coercion-modules.tex — the 7 primitives + object, typeof quirks, == vs === vs
% Object.is, ToPrimitive and the coercion puzzles (traced), truthy/falsy, IEEE-754 numbers and
% BigInt, ?? / || / ?., value vs reference and copying, ESM vs CommonJS, circular imports.
% Source: own knowledge, aligned with ECMAScript (IsLooselyEqual, ToPrimitive,
% OrdinaryToPrimitive, module Link/Evaluate). Senior-interview level.
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/javascript/js-types-coercion-modules.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=javascript kind=concept level=core platform=web new=no round=typescript-2026-09-24 topic=language
% @tags: type-coercion, loose-equality, toprimitive, object-is, typeof, falsy-values, ieee-754, bigint, nullish-coalescing, optional-chaining, structuredclone, esm-vs-commonjs
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}

\lstdefinelanguage{TSSheet}{
  morekeywords={import,from,export,default,const,let,var,type,interface,extends,function,
    return,async,await,if,else,switch,case,new,true,false,null,undefined,typeof,keyof,
    as,satisfies,never,string,number,boolean,void,declare,namespace,readonly,class,this,
    for,of,in,static,super,yield,delete,throw,try,catch,require,module},
  sensitive=true, morecomment=[l]{//}, morecomment=[s]{/*}{*/},
  morestring=[b]", morestring=[b]', morestring=[b]`}

\tikzset{
  sb/.style={box, font=\scriptsize, inner sep=1.5pt, minimum height=4.2mm},
  lbl/.style={font=\tiny, text=black!75, inner sep=1pt, align=center},
  pt/.style={font=\bfseries\small, anchor=west},
  tr/.style={sb, font=\scriptsize\ttfamily, minimum width=19mm},
}
% operators split into boxes so 0xProto cannot turn them into ligature glyphs
\newcommand\eqq{\hbox{=}\hbox{=}}
\newcommand\eqqq{\hbox{=}\hbox{=}\hbox{=}}
\newcommand\neqq{\hbox{!}\hbox{=}\hbox{=}}
\newcommand\qq{\hbox{?}\hbox{?}}
\newcommand\oo{\hbox{|}\hbox{|}}
\newcommand\qd{\hbox{?}\hbox{.}}
\newcommand\gee{\hbox{>}\hbox{=}}

\begin{document}

\sheettitle{Types, coercion and modules}{javascript · memo}

\oneliner{Seven primitives (immutable, by value) + \textbf{object} (by reference). Coercion runs
a few spec algorithms — \textbf{ToPrimitive}, \textbf{ToNumber}, \textbf{IsLooselyEqual} — so
every ``WAT'' is traceable. ESM imports are \textbf{live bindings}; \texttt{require} returns an object.}

\vspace{2pt}
\noindent\begin{tikzpicture}[sheet]
  \foreach \x in {6.0,11.0} \draw[sheetGrey!40] (\x,3.1) -- (\x,-0.45);
  % ── 1 trace [] == ![] ──
  \node[pt] at (0,2.95) {\textcolor{sheetBlue}{1} \texttt{[] \eqq{} ![]} step by step};
  \node[tr] (a) at (0.9,2.4) {[] \eqq{} ![]};
  \node[tr] (b) at (0.9,1.5) {[] \eqq{} false};
  \node[tr] (c) at (0.9,0.6) {[] \eqq{} 0};
  \node[tr] (d) at (4.3,0.6) {"" \eqq{} 0};
  \node[tr] (e) at (4.3,1.5) {0 \eqq{} 0};
  \node[sb, minimum width=19mm, draw=sheetGreen, fill=sheetGreen!10, font=\scriptsize\ttfamily\bfseries] (f) at (4.3,2.4) {true};
  \draw[flow] (a) -- node[lbl, right, align=left]{\texttt{!} runs first: objects\\are truthy $\to$ \texttt{false}} (b);
  \draw[flow] (b) -- node[lbl, right, align=left]{boolean $\to$ ToNumber} (c);
  \draw[flow] (c) -- node[lbl, below, align=center]{object $\to$\\ToPrimitive} (d);
  \draw[flow] (d) -- node[lbl, right, align=left]{string $\to$\\ToNumber} (e);
  \draw[flow] (e) -- (f);
  \node[lbl, anchor=west, text=sheetBrown] at (0,-0.2) {\texttt{[].valueOf()} is the array (not primitive) $\to$ \texttt{toString()} $\to$ \texttt{""}};
  % ── 2 ToPrimitive ──
  \node[pt] at (6.1,2.95) {\textcolor{sheetBlue}{2} \texttt{ToPrimitive(obj, hint)}};
  \node[sb, minimum width=25mm, anchor=west] (h) at (6.15,2.35) {has \texttt{[Symbol.toPrimitive]}?};
  \node[sb, minimum width=13mm, anchor=west, draw=sheetGreen, fill=sheetGreen!10] (h1) at (9.55,2.35) {call(hint)};
  \node[sb, minimum width=25mm, anchor=west] (s) at (6.15,1.55) {hint \texttt{"string"}?};
  \node[sb, minimum width=21mm, anchor=west, font=\scriptsize\ttfamily] (s1) at (8.55,1.1) {toString, valueOf};
  \node[sb, minimum width=21mm, anchor=west, font=\scriptsize\ttfamily] (s2) at (6.15,0.55) {valueOf, toString};
  \draw[flow] (h) -- node[lbl, above]{yes} (h1);
  \draw[flow] (h) -- node[lbl, left]{no} (s);
  \draw[flow] (s.east) -| node[lbl, above, pos=0.3]{yes} (s1.north);
  \draw[flow] (s) -- node[lbl, left]{no} (s2);
  \node[lbl, anchor=west, align=left] at (8.5,0.45) {first \emph{primitive} result wins;\\neither $\to$ TypeError};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (6.1,-0.2) {hint: \texttt{String()}, template = string · math, unary \texttt{+} = number ·};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (6.1,-0.42) {binary \texttt{+}, \texttt{\eqq} = default (acts as number; Date as string)};
  % ── 3 ESM vs CJS ──
  \node[pt] at (11.1,2.95) {\textcolor{sheetBlue}{3} live binding vs copy};
  \node[sb, minimum width=30mm, align=left, font=\tiny\ttfamily] (m) at (13.75,2.2) {// counter module\\export let n = 0;\\export const inc = () \hbox{=}\hbox{>} n++;};
  \node[sb, minimum width=21mm, draw=sheetGreen, fill=sheetGreen!10, font=\tiny] (esm) at (12.2,1.0) {ESM \texttt{import \{n\}}\\sees \textbf{1} after \texttt{inc()}};
  \node[sb, minimum width=21mm, draw=sheetRed, fill=sheetRed!7, font=\tiny] (cjs) at (15.3,1.0) {CJS \texttt{const \{n\} =}\\\texttt{require()}: still \textbf{0}};
  \draw[hot] (esm.north) -- node[lbl, left]{same slot} (esm.north|-m.south);
  \draw[flow, dashed] (cjs.north) -- node[lbl, right]{copied} (cjs.north|-m.south);
  \node[lbl, anchor=west, align=left] at (11.1,0.35) {ESM: parse $\to$ \textbf{link} all imports/exports $\to$ evaluate};
  \node[lbl, anchor=west, align=left] at (11.1,0.1) {deps first. Importers can't assign \texttt{n} (TypeError).};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (11.1,-0.2) {CJS: run the file on \texttt{require}, cache};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (11.1,-0.42) {\texttt{module.exports}, return that object};
\end{tikzpicture}

\begin{multicols}{2}

\section{Types and equality}
\begin{itemize}\raggedright
  \item Primitives: \texttt{string number bigint boolean undefined symbol null}; everything else
        is an object (arrays, functions, dates). \texttt{"a".length} works by temporary boxing.
  \item \texttt{typeof}: \texttt{null} $\to$ \texttt{"object"} (a 1995 tag bug, kept forever) ·
        functions $\to$ \texttt{"function"} · arrays $\to$ \texttt{"object"}
        (\texttt{Array.isArray}) · undeclared $\to$ \texttt{"undefined"} (a TDZ name throws).
  \item \texttt{\eqqq}: no coercion. \texttt{\eqq} (IsLooselyEqual): same type $\to$
        \texttt{\eqqq}; \texttt{null \eqq{} undefined} and nothing else; number vs string
        $\to$ ToNumber; boolean $\to$ ToNumber \emph{first}; object vs primitive $\to$ ToPrimitive;
        then repeat.
  \item \texttt{NaN \neqq{} NaN}, \texttt{+0 \eqqq{} -0}. \texttt{Object.is} fixes both;
        \textbf{SameValueZero} (\texttt{includes}, \texttt{Map}/\texttt{Set} keys): NaN
        equals NaN, $\pm$0 equal. \texttt{indexOf(NaN)} is $-1$, \texttt{includes(NaN)} true.
  \item \textbf{Falsy}: \texttt{false 0 -0 0n "" null undefined NaN} — nothing else.
        \texttt{"0"}, \texttt{"false"}, \texttt{[]}, \texttt{\{\}} are truthy.
\end{itemize}

\section{The puzzles — and why}
{\scriptsize\setlength{\tabcolsep}{2.5pt}\renewcommand{\arraystretch}{1.05}
\begin{tabular}{@{}>{\ttfamily}l>{\ttfamily}l>{\raggedright\arraybackslash}p{40mm}@{}}
\toprule
\normalfont expression & \normalfont result & why \\ \midrule
{}[] + \{\} & "[object Object]" & both $\to$ strings (\texttt{""}, \texttt{"[object Object]"}), concat \\
\{\} + [] & 0 \normalfont\emph{(statement)} & leading \texttt{\{\}} is a \emph{block}; \texttt{+[]} $\to$ 0 \\
{}[] + [] & "" & \texttt{"" + ""} \\
{}[1,2] + [3] & "1,23" & arrays join with commas \\
"5" - 2 {\normalfont or} "5" + 2 & 3 {\normalfont or} "52" & \texttt{-} is numeric only; \texttt{+} prefers strings \\
+"" · +" 12 " · +"12px" & 0 · 12 · NaN & ToNumber trims; whole string must parse \\
null + 1 · undefined + 1 & 1 · NaN & null $\to$ 0, undefined $\to$ NaN \\
null \eqq{} 0 · null \gee{} 0 & false · true & \texttt{\eqq} special-cases null; \texttt{\gee} does ToNumber \\
{}[10,1,2].sort() & {}[1,10,2] & default sort compares \emph{strings} \\
"b"+"a"+ +"a"+"a" & "baNaNa" & \texttt{+"a"} is NaN \\
0.1 + 0.2 \eqqq{} 0.3 & false & 0.30000000000000004 \\ \bottomrule
\end{tabular}}

\section{Numbers}
\begin{itemize}\raggedright
  \item One \texttt{number}: IEEE-754 double. Integers exact up to
        \texttt{Number.MAX\_SAFE\_INTEGER} $= 2^{53}-1$; \texttt{2**53 + 1 \eqqq{} 2**53}.
        Compare floats with a tolerance (\texttt{Number.EPSILON} $= 2^{-52}$ is relative).
  \item \texttt{BigInt} (\texttt{10n}, ES2020): arbitrary precision; mixing with number in
        arithmetic throws TypeError; \texttt{JSON.stringify} throws. IDs from a 64-bit backend
        $\to$ send as strings.
  \item Bitwise ops go through Int32: \texttt{2**31 | 0} is negative.
\end{itemize}

\section{\texttt{\qq}, \texttt{\oo}, \texttt{\qd}}
\raggedright\texttt{a \oo{} d} takes \texttt{d} for \emph{any} falsy \texttt{a} (so \texttt{0}, \texttt{""}
are lost); \texttt{a \qq{} d} only for \texttt{null}/\texttt{undefined} (ES2020). Mixing
\texttt{\qq} with \texttt{\oo}/\texttt{\&\&} needs parens (SyntaxError).
\texttt{a\qd b.c} short-circuits the \emph{whole} chain to \texttt{undefined};
\texttt{f\qd()}, \texttt{o\qd[k]}; not assignable. \texttt{\qq=}, \texttt{\oo=}, \texttt{\&\&=} (ES2021).

\section{Value vs reference, copying}
\raggedright Primitives copy the value; objects copy the \emph{reference} (``call by sharing'': mutating a
param is visible to the caller, reassigning it is not). \textbf{Swift:} no structs here — every
object behaves like a \texttt{class}, so value semantics are by convention (spread, freeze,
immutable updates). \texttt{\{...o\}}, \texttt{[...a]}, \texttt{Object.assign} are
\textbf{shallow}. \texttt{structuredClone} is deep: cycles, \texttt{Map}/\texttt{Set},
\texttt{Date}, typed arrays; throws on functions; class instances come back as plain objects.
\texttt{JSON} round-trip drops \texttt{undefined}/functions, turns Dates into strings, throws
on cycles and BigInt.

\section{Modules — ESM vs CommonJS}
{\scriptsize\setlength{\tabcolsep}{2.5pt}
\begin{tabular}{@{}>{\raggedright}p{13mm}>{\raggedright}p{28mm}>{\raggedright\arraybackslash}p{28mm}@{}}
\toprule
 & ESM \texttt{import}/\texttt{export} & CJS \texttt{require}/\texttt{module.exports} \\ \midrule
when & static, top level, linked before run; hoisted & a runtime call; anywhere, conditional \\
bindings & \textbf{live}, read-only & a plain object; destructuring copies \\
loading & async-capable; top-level \texttt{await} & synchronous \\
\texttt{this}, mode & \texttt{undefined}, always strict & \texttt{module.exports}, sloppy \\
tree-shaking & yes (static shape) & hard \\ \bottomrule
\end{tabular}}
\begin{itemize}\raggedright
  \item \textbf{Circular}: CJS gets the \emph{partially filled} \texttt{exports} (missing keys
        \texttt{undefined}); ESM: a \texttt{let/const/class} read before its module ran throws
        ReferenceError (TDZ); function declarations work (hoisted).
  \item \textbf{Interop}: \texttt{import x from 'cjs'} = \texttt{module.exports}; CJS named
        imports are guessed. \texttt{require(esm)} was \texttt{ERR\_REQUIRE\_ESM} ($\to$
        \texttt{import()}); bundlers/TS fake ESM via \texttt{\_\_esModule} — the \texttt{.default} mess.
  \item \texttt{import()} (both systems) $\to$ promise of the namespace: lazy/split loading.
\end{itemize}

\section{Interview traps}
\begin{itemize}\raggedright
  \trap{\texttt{count \oo{} 10} with \texttt{count = 0} gives 10 — use \texttt{\qq}.}
  \trap{\texttt{typeof null} is \texttt{"object"}; \texttt{x !\hbox{=} null} checks both nullish.}
  \trap{Top-level await in a shared module blocks \emph{every} importer's evaluation.}
\end{itemize}

\section{Remember}
\emph{\texttt{\eqqq} by default, \texttt{\qq} for defaults · ToPrimitive then ToNumber · doubles
lie after $2^{53}$ · spread is shallow · ESM links, CJS runs.}

\section{Likely questions}
\begin{enumerate}\raggedright
  \item \texttt{\eqq} vs \texttt{\eqqq} vs \texttt{Object.is}? — coerce · strict · strict + NaN, $\pm$0.
  \item Why is \texttt{[] \eqq{} ![]} true? — drawing 1.
  \item ESM vs CJS? — static + live + async-capable vs runtime + copied + sync.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} js-scope-closures-this (TDZ) ·
js-prototypes-objects-classes (\texttt{Symbol.toPrimitive}) · js-async (top-level await) ·
rn-testing-typescript · Swift value vs reference types}

\end{document}
