<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eli Bendersky's website</title><link href="https://eli.thegreenplace.net/" rel="alternate"></link><link href="https://eli.thegreenplace.net/feeds/all.atom.xml" rel="self"></link><id>https://eli.thegreenplace.net/</id><updated>2026-04-10T02:28:00-07:00</updated><entry><title>watgo - a WebAssembly Toolkit for Go</title><link href="https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go/" rel="alternate"></link><published>2026-04-09T19:28:00-07:00</published><updated>2026-04-10T02:28:00-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-04-09:/2026/watgo-a-webassembly-toolkit-for-go/</id><summary type="html">&lt;p&gt;I'm happy to announce the general availability of &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;
- the &lt;strong&gt;W&lt;/strong&gt;eb&lt;strong&gt;A&lt;/strong&gt;ssembly &lt;strong&gt;T&lt;/strong&gt;oolkit for &lt;strong&gt;G&lt;/strong&gt;o. This project is similar to
&lt;a class="reference external" href="https://github.com/webassembly/wabt"&gt;wabt&lt;/a&gt; (C++) or
&lt;a class="reference external" href="https://github.com/bytecodealliance/wasm-tools"&gt;wasm-tools&lt;/a&gt; (Rust), but in
pure, zero-dependency Go.&lt;/p&gt;
&lt;p&gt;watgo comes with a CLI and a Go API to parse WAT (WebAssembly Text), validate
it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm happy to announce the general availability of &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;
- the &lt;strong&gt;W&lt;/strong&gt;eb&lt;strong&gt;A&lt;/strong&gt;ssembly &lt;strong&gt;T&lt;/strong&gt;oolkit for &lt;strong&gt;G&lt;/strong&gt;o. This project is similar to
&lt;a class="reference external" href="https://github.com/webassembly/wabt"&gt;wabt&lt;/a&gt; (C++) or
&lt;a class="reference external" href="https://github.com/bytecodealliance/wasm-tools"&gt;wasm-tools&lt;/a&gt; (Rust), but in
pure, zero-dependency Go.&lt;/p&gt;
&lt;p&gt;watgo comes with a CLI and a Go API to parse WAT (WebAssembly Text), validate
it, and encode it into WASM binaries; it also supports decoding WASM from its
binary format.&lt;/p&gt;
&lt;p&gt;At the center of it all is &lt;a class="reference external" href="https://pkg.go.dev/github.com/eliben/watgo/wasmir"&gt;wasmir&lt;/a&gt; - a semantic
representation of a WebAssembly module that users can examine (and manipulate).
This diagram shows the functionalities provided by watgo:&lt;/p&gt;
&lt;img alt="Block diagram showing the different parts of watgo; described in the next paragraph" class="align-center" src="https://eli.thegreenplace.net/images/2026/watgo-diagram.png" /&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Parse: a parser from WAT to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Validate: uses the official WebAssembly validation semantics to check that the
module is well formed and safe&lt;/li&gt;
&lt;li&gt;Encode: emits &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt; into WASM binary representation&lt;/li&gt;
&lt;li&gt;Decode: read WASM binary representation into &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="cli-use-case"&gt;
&lt;h2&gt;CLI use case&lt;/h2&gt;
&lt;p&gt;watgo comes with a CLI, which you can install by issuing this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;go install github.com/eliben/watgo/cmd/watgo@latest
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The CLI aims to be compatible with wasm-tools &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;, and I've already switched my
&lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples&lt;/a&gt; projects to
use it; e.g. a command to parse a WAT file, validate it and encode it into
binary format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;watgo parse stack.wat -o stack.wasm
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="api-use-case"&gt;
&lt;h2&gt;API use case&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt; semantically represents a WASM module with an API that's easy to work
with. Here's an example of using watgo to parse a simple WAT
program and do some analysis:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/eliben/watgo&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/eliben/watgo/wasmir&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;
&lt;span class="s"&gt;(module&lt;/span&gt;
&lt;span class="s"&gt;  (func (export &amp;quot;add&amp;quot;) (param i32 i32) (result i32)&lt;/span&gt;
&lt;span class="s"&gt;    local.get 0&lt;/span&gt;
&lt;span class="s"&gt;    local.get 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.add&lt;/span&gt;
&lt;span class="s"&gt;  )&lt;/span&gt;
&lt;span class="s"&gt;  (func (param f32 i32) (result i32)&lt;/span&gt;
&lt;span class="s"&gt;    local.get 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.const 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.add&lt;/span&gt;
&lt;span class="s"&gt;    drop&lt;/span&gt;
&lt;span class="s"&gt;    i32.const 0&lt;/span&gt;
&lt;span class="s"&gt;  )&lt;/span&gt;
&lt;span class="s"&gt;)`&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;watgo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ParseWAT&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasmText&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Module-defined functions carry a type index into m.Types. The function&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// body itself is a flat sequence of wasmir.Instruction values.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Funcs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeIdx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ValueKindI32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InstrLocalGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InstrI32Add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;module-defined funcs: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Funcs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;i32 params: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;local.get instructions: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;i32.add instructions: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One important note: the WAT format supports several syntactic niceties that
are flattened / canonicalized when lowered to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;. For example, all folded
instructions are lowered to unfolded ones (linear form), function &amp;amp; type
names are resolved to numeric indices, etc. This matches the validation and
execution semantics of WASM and its binary representation.&lt;/p&gt;
&lt;p&gt;These syntactic details are present in watgo in the &lt;tt class="docutils literal"&gt;textformat&lt;/tt&gt; package
(which parses WAT into an AST) and are removed when this is lowered to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;.
The &lt;tt class="docutils literal"&gt;textformat&lt;/tt&gt; package is kept internal at this time, but in the future I
may consider exposing it publicly - if there's interest.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-strategy"&gt;
&lt;h2&gt;Testing strategy&lt;/h2&gt;
&lt;p&gt;Even though it's still early days for watgo, I'm reasonably confident in its
correctness due to a strategy of very heavy testing right from the start.&lt;/p&gt;
&lt;p&gt;WebAssembly comes with a &lt;a class="reference external" href="https://github.com/WebAssembly/spec/"&gt;large official test suite&lt;/a&gt;,
which is perfect for end-to-end testing of new implementations.
The core test suite includes almost 200K lines of WAT files that carry several
modules with expected execution semantics and a variety of error scenarios
exercised. These live in specially designed &lt;a class="reference external" href="https://github.com/WebAssembly/spec/tree/main/interpreter#scripts"&gt;.wast files&lt;/a&gt; and
leverage a custom spec interpreter.&lt;/p&gt;
&lt;p&gt;watgo hijacks this approach by using the official test suite for its own
testing. A custom harness parses .wast files and uses watgo to convert the WAT
in them to binary WASM, which is then executed by Node.js &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;; this harness is
a significant effort in itself, but it's very much worth it - the result is
excellent testing coverage. watgo passes the entire WASM spec core test suite.&lt;/p&gt;
&lt;p&gt;Similarly, we leverage &lt;a class="reference external" href="https://github.com/WebAssembly/wabt/tree/main/test/interp"&gt;wabt's interp test suite&lt;/a&gt; which also
includes end-to-end tests, using a simpler Node-based harness to test them
against watgo.&lt;/p&gt;
&lt;p&gt;Finally, I maintain a collection of realistic program samples written in
WAT in the &lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples repository&lt;/a&gt;;
these are also used by watgo to test itself.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Though not all of wasm-tools's functionality is supported yet.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;To stick to a pure-Go approach also for testing, I originally tried
using wazero for this, but had to give up because wazero doesn't support
some of the recent WASM proposals that have already made it into the
standard (most notably Garbage Collection).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="WebAssembly"></category><category term="Compilation"></category></entry><entry><title>Summary of reading: January - March 2026</title><link href="https://eli.thegreenplace.net/2026/summary-of-reading-january-march-2026/" rel="alternate"></link><published>2026-03-31T17:34:00-07:00</published><updated>2026-04-02T22:07:22-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-03-31:/2026/summary-of-reading-january-march-2026/</id><summary type="html">&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;Intellectuals and Society&amp;quot; by Thomas Sowell - a collection of essays in which
Sowell criticizes &amp;quot;intellectuals&amp;quot;, by which he mostly means left-leaning
thinkers and opinions. Interesting, though certainly very biased. This book
is from 2009 and focuses mostly on early and mid 20th century; yes, history
certainly rhymes.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Hacker and …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;Intellectuals and Society&amp;quot; by Thomas Sowell - a collection of essays in which
Sowell criticizes &amp;quot;intellectuals&amp;quot;, by which he mostly means left-leaning
thinkers and opinions. Interesting, though certainly very biased. This book
is from 2009 and focuses mostly on early and mid 20th century; yes, history
certainly rhymes.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Hacker and the State: Cyber Attacks and the New Normal of Geopolitics&amp;quot;
by Ben Buchanan - a pretty good overview of some of the the major
cyber-attacks done by states in the past 15 years. It doesn't go very deep
because it's likely just based on the bits and pieces that leaked to the
press; for the same reason, the coverage is probably very partial. Still, it's
an interesting and well-researched book overall.&lt;/li&gt;
&lt;li&gt;&amp;quot;A Primate's Memoir: A Neuroscientist’s Unconventional Life Among the Baboons&amp;quot;
by Robert Sapolsky - an account of the author's years spent researching
baboons in Kenya. Only about a quarter of the book is really about baboons,
though; mostly, it's about the author's adventures in Africa (some of them
surely inspired by an intense death wish) and his interaction with the local
peoples. I really liked this book overall - it's engaging, educational and
funny. Should try more books by this author.&lt;/li&gt;
&lt;li&gt;&amp;quot;Seeing Like a State&amp;quot; by James C. Scott - the author attempts to link various
events in history to discuss &amp;quot;Why do well-intentioned plans for improving the
human condition go tragically awry?&amp;quot;; discussing large state plans like
scientific forest management, building pre-planned cities and mono-culture
agriculture. Some of the chapters are interesting, but overall I'm not sure
I'm sold on the thesis. Specifically, the author mixes in private enterprises
(like industrial agriculture in the West) with state-driven initiatives in
puzzling ways.&lt;/li&gt;
&lt;li&gt;&amp;quot;Karate-Do: My Way of Life&amp;quot; by Gichin Funakoshi - short autobiography from
the founder of modern Shotokan Karate. It's really interesting to find out
how recent it all is - prior to WWII, Karate was an obscure art practiced
mostly in Okinawa and a bit in other parts of Japan. The author played a
critical role in popularizing Karate and spreading it out of Okinawa in the
first half of the 20th century. The writing is flowing and succinct - I really
liked this book.&lt;/li&gt;
&lt;li&gt;&amp;quot;A Tale of a Ring&amp;quot; by Ilan Sheinfeld (read in Hebrew) - a multi-generational
fictional saga of two families who moved from Danzig (today Gdansk in Poland)
to Buenos Aires in late 19th century, with a touch of magic. Didn't like this
one very much.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Wide Wide Sea: Imperial Ambition, First Contact and the Fateful Final
Voyage of Captain James Cook&amp;quot; by Hampton Sides - a very interesting account
of Captain Cook's last voyage (the one tasked with finding a northwest passage
around Canada). The book has a strong focus on his interaction with
Polynesian peoples along the way, especially on Hawaii (which he was the first
European to visit).&lt;/li&gt;
&lt;li&gt;&amp;quot;The Suitcase&amp;quot; by Sergei Dovlatov - (read in Russian) a collection of short
stories in Dovlatov's typical humorist style. Very nice little book.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Second Chance Convenience Store&amp;quot; by Kim Ho-Yeon - a collection of
connected stories centered around a convenience store in Seoul, and an unusual
new employee that began working night shifts there. Short and sweet fiction,
I enjoyed it.&lt;/li&gt;
&lt;li&gt;&amp;quot;A History of the Bible: The Story of the World's Most Influential Book&amp;quot; by
John Barton - a very detailed history of the Bible, covering both the old and
new testaments in many aspects. Some parts of the book are quite tedious; it's
not an easy read. Even though the author tries to maintain a very objective
and scientific approach, it's apparent (at least for an atheist) that he
skirts as close as possible to declaring it all nonsense, given that he's
a priest!&lt;/li&gt;
&lt;li&gt;&amp;quot;Rust Atomics and Locks: Low-Level Concurrency in Practice&amp;quot; by Mara Bos - an
overview of low-level concurrency topics using Rust. It's a decent book for
people not too familiar with the subject; I personally didn't find it too
captivating, but I do see the possibility of referring to it in the future if
I get to do some lower-level Rust hacking. A comment on the code samples: it
would be nice if the accompanying repository had test harnesses to observe how
the code behaves, and some benchmarks. Without this, many claims made in the
book feel empty without real data to back them up, and it's challenging to
play with the code and see it perform in real life.&lt;/li&gt;
&lt;li&gt;&amp;quot;Hot Chocolate on Thursday&amp;quot; by Michiko Aoyama - a bit similar to &amp;quot;What You Are
Looking for Is in the Library&amp;quot; by the same author: connected short stories
about ordinary people living their life in Japan (with one detour to
Australia). Slightly worse than the previous book, but still pretty good.&lt;/li&gt;
&lt;li&gt;&amp;quot;The Silmarillion&amp;quot; by J.R.R. Tolkien - even though I'm a big LOTR fan, I've
never gotten myself to read this one, due to its reputation for being
difficult. What changed things eventually (25 years after my first read
through of LOTR) is my kids! They liked LOTR so much that they went straight
ahead to Silmarillion and burned through it as well, so I couldn't stay
behind. What can I say, this book is pretty amazing. The amazing thing is how
a book can be both epic and borderline unreadable at the same time :) Tolkien
really let himself go with the names here (3-4 new names introduced per page,
on average), names for characters, names for natural features like forests
and rivers, names for all kinds of magical paraphernalia; names that change in
time, different names given to the same thing by different peoples, and on
and on. The edition I was reading has a helpful name index at the end (42
pages long!) which was very helpful, but it still made the task only
marginally easier. Names aside though, the book is undoubtedly monumental; the
language is outstanding. It's a whole new mythology, Bible-like in scope, all
somehow more-or-less consistent (if you remember who is who, of course); it's
an injustice to see this just as a prelude to the LOTR books. Compared to the
scope of the Silmarillion, LOTR is just a small speck of a quest told in
detail; The Silmarillion - among other things - includes brief tellings of
at least a dozen stories of similar scope. Many modern book (or TV) series
build whole &amp;quot;universes&amp;quot; with their own rules, history and aesthetic. The
Silmarillion must be considered the OG of this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Re-reads:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;Travels with Charley in Search of America&amp;quot; by John Steinbeck&lt;/li&gt;
&lt;li&gt;&amp;quot;Deep Work&amp;quot; by Cal Newport&lt;/li&gt;
&lt;li&gt;&amp;quot;The Philadelphia chromosome&amp;quot; by Jessica Wapner&lt;/li&gt;
&lt;li&gt;&amp;quot;The Price of Privilege&amp;quot; by Madeline Levine&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Book reviews"></category></entry><entry><title>Notes on Lagrange Interpolating Polynomials</title><link href="https://eli.thegreenplace.net/2026/notes-on-lagrange-interpolating-polynomials/" rel="alternate"></link><published>2026-02-28T18:58:00-08:00</published><updated>2026-03-05T13:31:03-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-02-28:/2026/notes-on-lagrange-interpolating-polynomials/</id><summary type="html">&lt;p&gt;&lt;em&gt;Polynomial interpolation&lt;/em&gt; is a method of finding a polynomial function
that fits a given set of data perfectly. More concretely, suppose we
have a set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; distinct points &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/e3ee741ff781bf26237c69b505322eb378075e89.svg" style="height: 19px;" type="image/svg+xml"&gt;\[(x_0,y_0), (x_1, y_1), (x_2, y_2)\cdots(x_n, y_n)\]&lt;/object&gt;
&lt;p&gt;And we want to find the polynomial coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/3cb777352d19998f6099864dfe85849e46fd0d8c.svg" style="height: 11px;" type="image/svg+xml"&gt;{a_0\cdots …&lt;/object&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Polynomial interpolation&lt;/em&gt; is a method of finding a polynomial function
that fits a given set of data perfectly. More concretely, suppose we
have a set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; distinct points &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/e3ee741ff781bf26237c69b505322eb378075e89.svg" style="height: 19px;" type="image/svg+xml"&gt;\[(x_0,y_0), (x_1, y_1), (x_2, y_2)\cdots(x_n, y_n)\]&lt;/object&gt;
&lt;p&gt;And we want to find the polynomial coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/3cb777352d19998f6099864dfe85849e46fd0d8c.svg" style="height: 11px;" type="image/svg+xml"&gt;{a_0\cdots a_n}&lt;/object&gt;
such that:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/4aff81770a1cc10239926be6d6676eb37fba719d.svg" style="height: 22px;" type="image/svg+xml"&gt;\[p(x)=a_0 + a_1 x + a_2 x^2 + \cdots + a_n x^n\]&lt;/object&gt;
&lt;p&gt;Fits all our points; that is &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/9bee0b2482233e55d638019ff5324f45ce5c0134.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x_0)=y_0&lt;/object&gt;, &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/1066a27080e8f4f98e1e837c563063ce09929300.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x_1)=y_1&lt;/object&gt; etc.&lt;/p&gt;
&lt;p&gt;This post discusses a common approach to solving this problem, and also
shows why such a polynomial exists and is unique.&lt;/p&gt;
&lt;div class="section" id="showing-existence-using-linear-algebra"&gt;
&lt;h2&gt;Showing existence using linear algebra&lt;/h2&gt;
&lt;p&gt;When we assign all points &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/fac14e12be52903f4008e439178230b3eefb437a.svg" style="height: 19px;" type="image/svg+xml"&gt;(x_i, y_i)&lt;/object&gt; into the generic polynomial
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;, we get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/708bd02c5cc4f2ddb0dbc93967053e55853683d3.svg" style="height: 134px;" type="image/svg+xml"&gt;\[\begin{aligned}
p(x_0)&amp;amp;=a_0 + a_1 x_0 + a_2 x_0^2 + \cdots a_n x_0^n = y_0\\
p(x_1)&amp;amp;=a_0 + a_1 x_1 + a_2 x_1^2 + \cdots a_n x_1^n = y_1\\
p(x_2)&amp;amp;=a_0 + a_1 x_2 + a_2 x_2^2 + \cdots a_n x_2^n = y_2\\
\cdots \\
p(x_n)&amp;amp;=a_0 + a_1 x_n + a_2 x_n^2 + \cdots a_n x_n^n = y_n\\
\end{aligned}\]&lt;/object&gt;
&lt;p&gt;We want to solve for the coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1ba9b59bdee92f38c1698c784b67ba70f803331d.svg" style="height: 11px;" type="image/svg+xml"&gt;a_i&lt;/object&gt;. This is a linear
system of equations that can be represented by the following matrix
equation:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/4352a3b8e07b33cc419089f1c48e0b360c9200e5.svg" style="height: 159px;" type="image/svg+xml"&gt;\[{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
     1 &amp;amp; x_0 &amp;amp; x_0^2 &amp;amp; \dots &amp;amp; x_0^n\\
     1 &amp;amp; x_1 &amp;amp; x_1^2 &amp;amp; \dots &amp;amp; x_1^n\\
     1 &amp;amp; x_2 &amp;amp; x_2^2 &amp;amp; \dots &amp;amp; x_2^n\\
     \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
     1 &amp;amp; x_n &amp;amp; x_n^2 &amp;amp; \dots &amp;amp; x_n^n
 \end{bmatrix}
 \begin{bmatrix}
     a_0\\
     a_1\\
     a_2\\
     \vdots\\
     a_n\\
 \end{bmatrix}=
 \begin{bmatrix}
     y_0\\
     y_1\\
     y_2\\
     \vdots\\
     y_n\\
 \end{bmatrix}
 }\]&lt;/object&gt;
&lt;p&gt;The matrix on the left is called the &lt;em&gt;Vandermonde matrix&lt;/em&gt;. This matrix
is known to be invertible (see Appendix for a proof); therefore, this
system of equations has a single solution that can be calculated by
inverting the matrix.&lt;/p&gt;
&lt;p&gt;In practice, however, the Vandermonde matrix is often numerically
ill-conditioned, so inverting it isn’t the best way to calculate exact
polynomial coefficients. Several better methods exist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lagrange-polynomial"&gt;
&lt;h2&gt;Lagrange Polynomial&lt;/h2&gt;
&lt;p&gt;Lagrange interpolation polynomials emerge from a simple, yet powerful
idea. Let’s define the &lt;em&gt;Lagrange basis&lt;/em&gt; functions &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;
(&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/71047ffb369f30ea7df6aad7d69de2edc1eca912.svg" style="height: 18px;" type="image/svg+xml"&gt;i \in [0, n]&lt;/object&gt;) as follows, given our points &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/fac14e12be52903f4008e439178230b3eefb437a.svg" style="height: 19px;" type="image/svg+xml"&gt;(x_i, y_i)&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/5e4d0981c95e59436de02cb6fc85dcdcf4537f1b.svg" style="height: 54px;" type="image/svg+xml"&gt;\[l_i(x) =
\begin{cases}
    1      &amp;amp; x = x_i \\
    0      &amp;amp; x = x_j \quad \forall j \neq i
\end{cases}\]&lt;/object&gt;
&lt;p&gt;In words, &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt; is constrained to 1 at &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; and to 0 at
all other &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/73058e43db0f4edc791b10f27f913cbc5d361ab6.svg" style="height: 14px;" type="image/svg+xml"&gt;x_j&lt;/object&gt;. We don’t care about its value at any other point.&lt;/p&gt;
&lt;p&gt;The linear combination:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9e02d7ad79abe9dd4e108d6e33e02bd6b2cece5e.svg" style="height: 49px;" type="image/svg+xml"&gt;\[p(x)=\sum_{i=0}^{n}y_i l_i(x)\]&lt;/object&gt;
&lt;p&gt;is then a valid interpolating polynomial for our set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt;
points, because it’s equal to &lt;img alt="y_i" class="valign-m4" src="https://eli.thegreenplace.net/images/math/35c2ac2f82d0ff8f9011b596ed7e54bfcc55f471.png" style="height: 12px;" /&gt; at each &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; (take a
moment to convince yourself this is true).&lt;/p&gt;
&lt;p&gt;How do we find &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;? The key insight comes from studying the
following function:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/ab2f106bd0be79644b4f08e7241d53541fde914c.svg" style="height: 54px;" type="image/svg+xml"&gt;\[l&amp;#x27;_i(x)=(x-x_0)\cdot (x-x_1)\cdots (x-x_{i-1}) \cdot (x-x_{i+1})\cdots (x-x_n)=
\prod_{\substack{0\leq j \leq n \\ j \neq i}}(x-x_j)\]&lt;/object&gt;
&lt;p&gt;This function has &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt; terms &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/ee95074369b77b297d18a5d2500b53463aaf275a.svg" style="height: 20px;" type="image/svg+xml"&gt;(x-x_j)&lt;/object&gt; for all
&lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/6e1be82fe5bc74efdbe2bd9234f5da2cec90f954.svg" style="height: 17px;" type="image/svg+xml"&gt;j\neq i&lt;/object&gt;. It should be easy to see that &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/f2445af2b56e69160a5ee45a30d1a96a97ea8496.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_i(x)&lt;/object&gt; is 0 at
all &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/73058e43db0f4edc791b10f27f913cbc5d361ab6.svg" style="height: 14px;" type="image/svg+xml"&gt;x_j&lt;/object&gt; when &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/6e1be82fe5bc74efdbe2bd9234f5da2cec90f954.svg" style="height: 17px;" type="image/svg+xml"&gt;j\neq i&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;What about its value at &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt;, though? We can just assign
&lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; into &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/f2445af2b56e69160a5ee45a30d1a96a97ea8496.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_i(x)&lt;/object&gt; to get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/c4a71af1b8a616db5347164e5abc488dc888da60.svg" style="height: 54px;" type="image/svg+xml"&gt;\[l&amp;#x27;_i(x_i)=\prod_{\substack{0\leq j \leq n \\ j \neq i}}(x_i-x_j)\]&lt;/object&gt;
&lt;p&gt;And then normalize &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/f2445af2b56e69160a5ee45a30d1a96a97ea8496.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_i(x)&lt;/object&gt;, dividing it by this (constant) value. We get
the Lagrange basis function &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9556fe744ec99f27b47d421edd5e625e072145b5.svg" style="height: 64px;" type="image/svg+xml"&gt;\[l_i(x)=\frac{l&amp;#x27;_i(x)}{l&amp;#x27;_i(x_i)}=\prod_{\substack{0\leq j \leq n \\ j \neq i}}\frac{x-x_j}{x_i-x_j}\]&lt;/object&gt;
&lt;p&gt;Let’s use a concrete example to visualize this. Suppose we have the
following set of points we want to interpolate:
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/eb71287f6f652c4e2753483486714619516e0822.svg" style="height: 19px;" type="image/svg+xml"&gt;(1,4), (2,2), (3,3)&lt;/object&gt;. We can calculate &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/efa94b9705d7324c446e2fd3a0f87163cf4a09aa.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_0(x)&lt;/object&gt;,
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/35fec30e61d70678416c62035d3dce29fd3ffc5a.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_1(x)&lt;/object&gt; and &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/5d4342750343edd5a8607e54a1f6dac771110497.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_2(x)&lt;/object&gt;, and get the following:&lt;/p&gt;
&lt;img alt="Un-normalized lagrange basis functions for our sample" class="align-center" src="https://eli.thegreenplace.net/images/2026/lagrange-basis.png" /&gt;
&lt;p&gt;Note where each &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/f2445af2b56e69160a5ee45a30d1a96a97ea8496.svg" style="height: 19px;" type="image/svg+xml"&gt;l&amp;#x27;_i(x)&lt;/object&gt; intersects the &lt;img alt="x" class="valign-0" src="https://eli.thegreenplace.net/images/math/11f6ad8ec52a2984abaafd7c3b516503785c2072.png" style="height: 8px;" /&gt; axis. These
functions have the right values at all &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/3430b1a7366e85d75a89e8ef95fffaa8e0fd36f2.svg" style="height: 14px;" type="image/svg+xml"&gt;x_{j\neq i}&lt;/object&gt;. If we
normalize them to obtain &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;, we get these functions:&lt;/p&gt;
&lt;img alt="Normalized lagrange basis functions for our sample" class="align-center" src="https://eli.thegreenplace.net/images/2026/lagrange-basis-normalized.png" /&gt;
&lt;p&gt;Note that each polynomial is 1 at the appropriate &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; and 0 at
all the other &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/3430b1a7366e85d75a89e8ef95fffaa8e0fd36f2.svg" style="height: 14px;" type="image/svg+xml"&gt;x_{j\neq i}&lt;/object&gt;, as required.&lt;/p&gt;
&lt;p&gt;With these &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;, we can now plot the interpolating polynomial
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/bc2c6fb1897affc253cf6db77c4f7d4a41a5be32.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)=\sum_{i=0}^{n}y_i l_i(x)&lt;/object&gt;, which fits our set of input points:&lt;/p&gt;
&lt;img alt="Interpolation polynomial" class="align-center" src="https://eli.thegreenplace.net/images/2026/lagrange-inter-poly.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="polynomial-degree-and-uniqueness"&gt;
&lt;h2&gt;Polynomial degree and uniqueness&lt;/h2&gt;
&lt;p&gt;We’ve just seen that the linear combination of Lagrange basis functions:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9e02d7ad79abe9dd4e108d6e33e02bd6b2cece5e.svg" style="height: 49px;" type="image/svg+xml"&gt;\[p(x)=\sum_{i=0}^{n}y_i l_i(x)\]&lt;/object&gt;
&lt;p&gt;is a valid interpolating polynomial for a set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; distinct
points &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/fac14e12be52903f4008e439178230b3eefb437a.svg" style="height: 19px;" type="image/svg+xml"&gt;(x_i, y_i)&lt;/object&gt;. What is its degree?&lt;/p&gt;
&lt;p&gt;Since the degree of each &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt; is &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt;, then the degree of
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt; is &lt;em&gt;at most&lt;/em&gt; &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt;. We’ve just derived the first part
of the &lt;em&gt;Polynomial interpolation theorem&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Polynomial interpolation theorem&lt;/strong&gt;: for any &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; data points
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/3e2e9500dcfffb808ec380f67db20f6f0804211e.svg" style="height: 20px;" type="image/svg+xml"&gt;(x_0,y_0), (x_1, y_1)\cdots(x_n, y_n) \in \mathbb{R}^2&lt;/object&gt; where no
two &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/73058e43db0f4edc791b10f27f913cbc5d361ab6.svg" style="height: 14px;" type="image/svg+xml"&gt;x_j&lt;/object&gt; are the same, there exists a unique polynomial
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt; of degree at most &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt; that interpolates these points.&lt;/p&gt;
&lt;p&gt;We’ve demonstrated existence and degree, but not yet &lt;em&gt;uniqueness&lt;/em&gt;. So
let’s turn to that.&lt;/p&gt;
&lt;p&gt;We know that &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt; interpolates all &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; points, and its
degree is &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt;. Suppose there’s another such polynomial
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/90425caaec1646540a7a9049146bf2606d9dbd0d.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)&lt;/object&gt;. Let’s construct:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/c77eca4deaf27dd00a5908a71840f50160ff7b4e.svg" style="height: 19px;" type="image/svg+xml"&gt;\[r(x)=p(x)-q(x)\]&lt;/object&gt;
&lt;p&gt;That do we know about &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/9468a2656a6201bfa194ec81fb0f78352c9666c9.svg" style="height: 19px;" type="image/svg+xml"&gt;r(x)&lt;/object&gt;? First of all, its value is 0 at all
our &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt;, so it has &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; &lt;em&gt;roots&lt;/em&gt;. Second, we also know
that its degree is at most &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt; (because it’s the difference of two
polynomials of such degree). These two facts are a contradiction.
No non-zero polynomial of degree &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/189de1175926fa11245f32e4d48aa2a7ab2435b4.svg" style="height: 15px;" type="image/svg+xml"&gt;\leq n&lt;/object&gt; can have
&lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; roots (a basic algebraic fact related to the &lt;em&gt;Fundamental
theorem of algebra&lt;/em&gt;). So &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/9468a2656a6201bfa194ec81fb0f78352c9666c9.svg" style="height: 19px;" type="image/svg+xml"&gt;r(x)&lt;/object&gt; must be the zero polynomial; in
other words, our &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt; is unique &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/4a4e9e431da45a27bc880a8a1ca44d8b1b9bc143.svg" style="height: 12px;" type="image/svg+xml"&gt;\blacksquare&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;Note the implication of uniqueness here: given our set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt;
distinct points, there’s only one polynomial of degree &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/189de1175926fa11245f32e4d48aa2a7ab2435b4.svg" style="height: 15px;" type="image/svg+xml"&gt;\leq n&lt;/object&gt;
that interpolates it. We can find its coefficients by inverting the
Vandermonde matrix, by using Lagrange basis functions, or
any other method &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lagrange-polynomials-as-a-basis-for-p-n-mathbb-r"&gt;
&lt;h2&gt;Lagrange polynomials as a basis for &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;&lt;/h2&gt;
&lt;p&gt;The set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; consists of all real polynomials of
degree &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/189de1175926fa11245f32e4d48aa2a7ab2435b4.svg" style="height: 15px;" type="image/svg+xml"&gt;\leq n&lt;/object&gt;. This set - along with addition of polynomials and
scalar multiplication - &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/notes-on-linear-algebra-for-polynomials/"&gt;forms a vector
space&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We called &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt; the &amp;quot;Lagrange basis&amp;quot; previously, and they do -
in fact - form an actual linear algebra basis for this vector space. To
prove this claim, we need to show that Lagrange polynomials are linearly
independent and that they span the space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linear independence&lt;/strong&gt;: we have to show that&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/3469cc3e26dc0483fb111f3993bf06b1c5336c53.svg" style="height: 49px;" type="image/svg+xml"&gt;\[s(x)=\sum_{i=0}^{n}a_i l_i(x)=0\]&lt;/object&gt;
&lt;p&gt;implies &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/936812b8cbbba06fa5948bf8f9393e0bd9abc223.svg" style="height: 15px;" type="image/svg+xml"&gt;a_i=0 \quad \forall i&lt;/object&gt;. Recall that &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt; is 1
at &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt;, while all other &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/1695428630aa1a460c0d7eb95749f04017fb8b60.svg" style="height: 20px;" type="image/svg+xml"&gt;l_j(x)&lt;/object&gt; are 0 at that point.
Therefore, evaluating &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/68cee1190d7058555e058756fed1d6527ab89855.svg" style="height: 19px;" type="image/svg+xml"&gt;s(x)&lt;/object&gt; at &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt;, we get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/eda879b68c484c7e88db094e5d4b63e38738503e.svg" style="height: 19px;" type="image/svg+xml"&gt;\[s(x_i)=a_i = 0\]&lt;/object&gt;
&lt;p&gt;Similarly, we can show that &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1ba9b59bdee92f38c1698c784b67ba70f803331d.svg" style="height: 11px;" type="image/svg+xml"&gt;a_i&lt;/object&gt; is 0, for all &lt;img alt="i" class="valign-0" src="https://eli.thegreenplace.net/images/math/042dc4512fa3d391c5170cf3aa61e6a638f84342.png" style="height: 12px;" /&gt;
&lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/4a4e9e431da45a27bc880a8a1ca44d8b1b9bc143.svg" style="height: 12px;" type="image/svg+xml"&gt;\blacksquare&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Span&lt;/strong&gt;: we’ve already demonstrated that the linear combination of
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9e02d7ad79abe9dd4e108d6e33e02bd6b2cece5e.svg" style="height: 49px;" type="image/svg+xml"&gt;\[p(x)=\sum_{i=0}^{n}y_i l_i(x)\]&lt;/object&gt;
&lt;p&gt;is a valid interpolating polynomial for any set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; distinct
points. Using the &lt;em&gt;polynomial interpolation theorem&lt;/em&gt;, this is the unique
polynomial interpolating this set of points. In other words, for every
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/ca8eba083582be9ac48f889214d2f74248955c2f.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)\in P_n(\mathbb{R})&lt;/object&gt;, we can identify any set of &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; distinct points it passes
through, and then use the technique described in this post to find the coefficients of &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/90425caaec1646540a7a9049146bf2606d9dbd0d.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)&lt;/object&gt; in the
Lagrange basis. Therefore, the set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/266681d17d2dc06fe4a139a6c0daa4c5c163b300.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x)&lt;/object&gt; spans
the vector space &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/4a4e9e431da45a27bc880a8a1ca44d8b1b9bc143.svg" style="height: 12px;" type="image/svg+xml"&gt;\blacksquare&lt;/object&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="interpolation-matrix-in-the-lagrange-basis"&gt;
&lt;h2&gt;Interpolation matrix in the Lagrange basis&lt;/h2&gt;
&lt;p&gt;Previously we’ve seen how to use the &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/dd30851f93c58a2397e24da06afb6c865f1fd4d6.svg" style="height: 20px;" type="image/svg+xml"&gt;\{1, x, x^2, \dots x^n\}&lt;/object&gt;
basis to write down a system of linear equations that helps us find the
interpolating polynomial. This results in the &lt;em&gt;Vandermonde matrix&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Using the Lagrange basis, we can get a much nicer matrix representation
of the interpolation equations.&lt;/p&gt;
&lt;p&gt;Recall that our general polynomial using the Lagrange basis is:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/e27577760eb802468c6135d93fa6b761cadd55ea.svg" style="height: 49px;" type="image/svg+xml"&gt;\[p(x)=\sum_{i=0}^{n}a_i l_i(x)\]&lt;/object&gt;
&lt;p&gt;Let’s build a system of equations for each of the &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; points
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/224e78a167ad90c8d6434766a835f152b7adcd44.svg" style="height: 19px;" type="image/svg+xml"&gt;(x_i,y_i)&lt;/object&gt;. For &lt;img alt="x_0" class="valign-m3" src="https://eli.thegreenplace.net/images/math/efbda784ad565c1c5201fdc948a570d0426bc6e6.png" style="height: 11px;" /&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/7a62a190b24f3e630c1d267c707dd96329efa545.svg" style="height: 49px;" type="image/svg+xml"&gt;\[p(x_0)=\sum_{i=0}^{n}a_i l_i(x_0)\]&lt;/object&gt;
&lt;p&gt;By definition of the Lagrange basis functions, all &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/dd57195bcdd0b0c2e0f836ee74730425bfd21726.svg" style="height: 19px;" type="image/svg+xml"&gt;l_i(x_0)&lt;/object&gt;
where &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/5722358d633306016b737900981a909e904098bd.svg" style="height: 17px;" type="image/svg+xml"&gt;i\neq 0&lt;/object&gt; are 0, while &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/e45ce17273faa377fac587c644355c089905f5f8.svg" style="height: 19px;" type="image/svg+xml"&gt;l_0(x_0)&lt;/object&gt; is 1. So this
simplifies to:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/c0536313cb1338dd7071c085f52ea51dcb72d79d.svg" style="height: 19px;" type="image/svg+xml"&gt;\[p(x_0)=a_0\]&lt;/object&gt;
&lt;p&gt;But the value at node &lt;img alt="x_0" class="valign-m3" src="https://eli.thegreenplace.net/images/math/efbda784ad565c1c5201fdc948a570d0426bc6e6.png" style="height: 11px;" /&gt; is &lt;img alt="y_0" class="valign-m4" src="https://eli.thegreenplace.net/images/math/2bb5817d0f3bf8490a8c7b1343f84f9635e683a3.png" style="height: 12px;" /&gt;, so we’ve just found
that &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/6df7c1c7fdab6f76ef16a230d6c0b3744017acfd.svg" style="height: 12px;" type="image/svg+xml"&gt;a_0=y_0&lt;/object&gt;. We can produce similar equations for the other
nodes as well, &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7d5f400d37852b1b57adaf0e21efabada38c8363.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x_1)=a_1&lt;/object&gt;, etc. In matrix form:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/d16c76a6af004459db78546a08032c278e9b2a6a.svg" style="height: 159px;" type="image/svg+xml"&gt;\[{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
     1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \dots &amp;amp; 0\\
     0 &amp;amp; 1 &amp;amp; 0 &amp;amp; \dots &amp;amp; 0\\
     0 &amp;amp; 0 &amp;amp; 1 &amp;amp; \dots &amp;amp; 0\\
     \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \dots &amp;amp; 1
 \end{bmatrix}
 \begin{bmatrix}
     a_0\\
     a_1\\
     a_2\\
     \vdots\\
     a_n\\
 \end{bmatrix}=
 \begin{bmatrix}
     y_0\\
     y_1\\
     y_2\\
     \vdots\\
     y_n\\
 \end{bmatrix}
 }\]&lt;/object&gt;
&lt;p&gt;We get the identity matrix; this is another way to trivially show that
&lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/6df7c1c7fdab6f76ef16a230d6c0b3744017acfd.svg" style="height: 12px;" type="image/svg+xml"&gt;a_0=y_0&lt;/object&gt;, &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/8491dd4445d9d585827151312ba90e75fc4859fb.svg" style="height: 12px;" type="image/svg+xml"&gt;a_1=y_1&lt;/object&gt; and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="appendix-vandermonde-matrix"&gt;
&lt;h2&gt;Appendix: Vandermonde matrix&lt;/h2&gt;
&lt;p&gt;Given some numbers &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/0f80294ca0e3cea0ce687973ea5aa7202c1c6f44.svg" style="height: 19px;" type="image/svg+xml"&gt;\{x_0 \dots x_n\}&lt;/object&gt; a matrix of this form:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/a1b91b44ffad5324694b37ed4ed07f5de048d55b.svg" style="height: 159px;" type="image/svg+xml"&gt;\[V=
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
1 &amp;amp; x_0 &amp;amp; x_0^2 &amp;amp; \dots &amp;amp; x_0^n\\
1 &amp;amp; x_1 &amp;amp; x_1^2 &amp;amp; \dots &amp;amp; x_1^n\\
1 &amp;amp; x_2 &amp;amp; x_2^2 &amp;amp; \dots &amp;amp; x_2^n\\
\vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
1 &amp;amp; x_n &amp;amp; x_n^2 &amp;amp; \dots &amp;amp; x_n^n
\end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;Is called the &lt;em&gt;Vandermonde&lt;/em&gt; matrix. What’s special about a Vandermonde
matrix is that we know it’s invertible when &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; are distinct.
This is &lt;a class="reference external" href="https://mathworld.wolfram.com/InvertibleMatrixTheorem.html"&gt;because its determinant is known to be
non-zero&lt;/a&gt;.
Moreover, its determinant is &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/aeef2af004a7aeeccdad775b2eaec44543643e90.svg" style="height: 41px;" type="image/svg+xml"&gt;\[\det(V) = \prod_{0 \le i &amp;lt; j \le n} (x_j - x_i)\]&lt;/object&gt;
&lt;p&gt;Here’s why.&lt;/p&gt;
&lt;p&gt;To get some intuition, let’s consider some small-rank Vandermonde
matrices. Starting with a 2-by-2:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/409bf1caaf2ceab8e097d007aacd2e7031185732.svg" style="height: 42px;" type="image/svg+xml"&gt;\[\det(V)=\det\begin{bmatrix}
1 &amp;amp; x_0 \\
1 &amp;amp; x_1 \\
\end{bmatrix}=x_1-x_0\]&lt;/object&gt;
&lt;p&gt;Let’s try 3-by-3 now:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/fa1e9fd7d521588ed1f13cd1ea96c43b56aef604.svg" style="height: 96px;" type="image/svg+xml"&gt;\[\det(V)=\det
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
    1 &amp;amp; x_0 &amp;amp; x_0^2 \\
    1 &amp;amp; x_1 &amp;amp; x_1^2 \\
    1 &amp;amp; x_2 &amp;amp; x_2^2 \\
\end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;We can use the standard way of calculating determinants to expand from
the first row:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/39f0107408e77d8492349b40106253d18487e48e.svg" style="height: 49px;" type="image/svg+xml"&gt;\[\begin{aligned}
\det(V)&amp;amp;=1\cdot(x_1 x_2^2 - x_2 x_1^2)-x_0(x_2^2-x_1^2)+x_0^2(x_2 - x_1)\\
&amp;amp;=x_1 x_2^2 - x_2 x_1^2 - x_0 x_2^2+x_0 x_1^2+x_0^2 x_2 - x_0^2 x_1\\
\end{aligned}\]&lt;/object&gt;
&lt;p&gt;Using some algebraic manipulation, it’s easy to show this is equivalent
to:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/1cfe17009b539affb81c76120ef3960119bf1a8d.svg" style="height: 19px;" type="image/svg+xml"&gt;\[\det(V)=(x_2-x_1)(x_2-x_0)(x_1-x_0)\]&lt;/object&gt;
&lt;p&gt;For the full proof, let’s look at the generalized
&lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt;-by-&lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; matrix again:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/d93183948364818eeb51b48056262a016e345ef9.svg" style="height: 159px;" type="image/svg+xml"&gt;\[V=
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
        1 &amp;amp; x_0 &amp;amp; x_0^2 &amp;amp; \dots &amp;amp; x_0^n\\
        1 &amp;amp; x_1 &amp;amp; x_1^2 &amp;amp; \dots &amp;amp; x_1^n\\
        1 &amp;amp; x_2 &amp;amp; x_2^2 &amp;amp; \dots &amp;amp; x_2^n\\
        \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
        1 &amp;amp; x_n &amp;amp; x_n^2 &amp;amp; \dots &amp;amp; x_n^n
    \end{bmatrix}
 }\]&lt;/object&gt;
&lt;p&gt;Recall that subtracting a multiple of one column from another doesn’t
change a matrix’s determinant. For each column &lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/1ce658ba27a5ceb614d1279b5e24989689505dcd.svg" style="height: 14px;" type="image/svg+xml"&gt;k&amp;gt;1&lt;/object&gt;, we’ll
subtract the value of column &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/4136a771b69c092ff42aa6115afbc9160e5353c9.svg" style="height: 12px;" type="image/svg+xml"&gt;k-1&lt;/object&gt; multiplied by &lt;img alt="x_0" class="valign-m3" src="https://eli.thegreenplace.net/images/math/efbda784ad565c1c5201fdc948a570d0426bc6e6.png" style="height: 11px;" /&gt; from
it (this is done on all columns simultaneously). The idea is to make the
first row all zeros after the very first element:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/7b17cf508ed83e3f00a056891c02d58bc85df72c.svg" style="height: 159px;" type="image/svg+xml"&gt;\[V=
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \dots &amp;amp; 0\\
        1 &amp;amp; x_1 - x_0 &amp;amp; x_1^2 - x_1 x_0&amp;amp; \dots &amp;amp; x_1^n - x_1^{n-1} x_0\\
        1 &amp;amp; x_2 - x_0 &amp;amp; x_2^2 - x_2 x_0&amp;amp; \dots &amp;amp; x_2^n - x_2^{n-1} x_0\\
        \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
        1 &amp;amp; x_n - x_0 &amp;amp; x_n^2 - x_n x_0&amp;amp; \dots &amp;amp; x_n^n - x_n^{n-1} x_0\\
    \end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;Now we factor out &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/89cce2d73854c97e220aa9b7232bb408e5d1b0d6.svg" style="height: 11px;" type="image/svg+xml"&gt;x_1-x_0&lt;/object&gt; from the second row (after the first
element), &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/9f4f5293a3d0b243932a97c1c52109c3bba378c7.svg" style="height: 11px;" type="image/svg+xml"&gt;x_2-x_0&lt;/object&gt; from the third row and so on, to get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/0f35e4bc0e43ffcebb6b8ca326dacba9c10549b6.svg" style="height: 159px;" type="image/svg+xml"&gt;\[V=
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \dots &amp;amp; 0\\
        1 &amp;amp; x_1 - x_0 &amp;amp; x_1(x_1 - x_0)&amp;amp; \dots &amp;amp; x_1^{n-1}(x_1 - x_0)\\
        1 &amp;amp; x_2 - x_0 &amp;amp; x_2(x_2 - x_0)&amp;amp; \dots &amp;amp; x_2^{n-1}(x_2 - x_0)\\
        \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
        1 &amp;amp; x_n - x_0 &amp;amp; x_n(x_n - x_0)&amp;amp; \dots &amp;amp; x_n^{n-1}(x_n - x_0)\\
    \end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;Imagine we erase the first row and first column of &lt;img alt="V" class="valign-0" src="https://eli.thegreenplace.net/images/math/c9ee5681d3c59f7541c27a38b67edf46259e187b.png" style="height: 12px;" /&gt;. We’ll call
the resulting matrix &lt;img alt="W" class="valign-0" src="https://eli.thegreenplace.net/images/math/e2415cb7f63df0c9de23362326ad3c37a9adfc96.png" style="height: 12px;" /&gt;.&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/bd6c93bd0a0454bce81c7565870e59033bffd317.svg" style="height: 128px;" type="image/svg+xml"&gt;\[W=
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
        x_1 - x_0 &amp;amp; x_1(x_1 - x_0)&amp;amp; \dots &amp;amp; x_1^{n-1}(x_1 - x_0)\\
        x_2 - x_0 &amp;amp; x_2(x_2 - x_0)&amp;amp; \dots &amp;amp; x_2^{n-1}(x_2 - x_0)\\
        \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
        x_n - x_0 &amp;amp; x_n(x_n - x_0)&amp;amp; \dots &amp;amp; x_n^{n-1}(x_n - x_0)\\
    \end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;Because the first row of &lt;img alt="V" class="valign-0" src="https://eli.thegreenplace.net/images/math/c9ee5681d3c59f7541c27a38b67edf46259e187b.png" style="height: 12px;" /&gt; is all zeros except the first
element, we have:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/c112a75142ecd4bc97e681cf01e4a734a67e9c5d.svg" style="height: 19px;" type="image/svg+xml"&gt;\[\det(V)=\det(W)\]&lt;/object&gt;
&lt;p&gt;Note that the first row of &lt;img alt="W" class="valign-0" src="https://eli.thegreenplace.net/images/math/e2415cb7f63df0c9de23362326ad3c37a9adfc96.png" style="height: 12px;" /&gt; has a common factor of
&lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/89cce2d73854c97e220aa9b7232bb408e5d1b0d6.svg" style="height: 11px;" type="image/svg+xml"&gt;x_1-x_0&lt;/object&gt;, so when calculating &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/3845b3a938b1cd6b8667c495a8caa9957a8ee224.svg" style="height: 19px;" type="image/svg+xml"&gt;\det(W)&lt;/object&gt;, we can move this
common factor out. Same for the common factor &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/9f4f5293a3d0b243932a97c1c52109c3bba378c7.svg" style="height: 11px;" type="image/svg+xml"&gt;x_2-x_0&lt;/object&gt; of the
second row, and so on. Overall, we can write:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/d55d4c9c9a66ca0f092d2bddf159c61b8db12896.svg" style="height: 128px;" type="image/svg+xml"&gt;\[\det(W)=(x_1-x_0)(x_2-x_0)\cdots(x_n-x_0)\cdot \det
{\renewcommand{\arraystretch}{1.5}\begin{bmatrix}
        1 &amp;amp; x_1 &amp;amp; x_1^2 &amp;amp; \dots &amp;amp; x_1^{n-1}\\
        1 &amp;amp; x_2 &amp;amp; x_2^2 &amp;amp; \dots &amp;amp; x_2^{n-1}\\
        \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp;\vdots \\
        1 &amp;amp; x_n &amp;amp; x_n^2 &amp;amp; \dots &amp;amp; x_n^{n-1}
    \end{bmatrix}
}\]&lt;/object&gt;
&lt;p&gt;But the smaller matrix is just the Vandermonde matrix for
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/280962e0b640e843643a0554e19a3938aec8cb31.svg" style="height: 19px;" type="image/svg+xml"&gt;\{x_1 \dots x_{n}\}&lt;/object&gt;. If we continue this process by induction,
we’ll get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/aeef2af004a7aeeccdad775b2eaec44543643e90.svg" style="height: 41px;" type="image/svg+xml"&gt;\[\det(V) = \prod_{0 \le i &amp;lt; j \le n} (x_j - x_i)\]&lt;/object&gt;
&lt;p&gt;If you’re interested, the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Vandermonde_matrix"&gt;Wikipedia page for the Vandermonde matrix&lt;/a&gt; has a couple of additional
proofs.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The &lt;img alt="x" class="valign-0" src="https://eli.thegreenplace.net/images/math/11f6ad8ec52a2984abaafd7c3b516503785c2072.png" style="height: 8px;" /&gt;-es here are called &lt;em&gt;nodes&lt;/em&gt; and the &lt;img alt="y" class="valign-m4" src="https://eli.thegreenplace.net/images/math/95cb0bfd2977c761298d9624e4b4d4c72a39974a.png" style="height: 12px;" /&gt;-s are
called &lt;em&gt;values&lt;/em&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://eli.thegreenplace.net/2024/method-of-differences-and-newton-polynomials/"&gt;Newton
polynomials&lt;/a&gt;
is also an option, and there are many other approaches.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Note that this means the product of all differences between
&lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/73058e43db0f4edc791b10f27f913cbc5d361ab6.svg" style="height: 14px;" type="image/svg+xml"&gt;x_j&lt;/object&gt; and &lt;img alt="x_i" class="valign-m3" src="https://eli.thegreenplace.net/images/math/34e03e6559b14df9fe5a97bbd2ed10109dfebbd3.png" style="height: 11px;" /&gt; where &lt;img alt="i" class="valign-0" src="https://eli.thegreenplace.net/images/math/042dc4512fa3d391c5170cf3aa61e6a638f84342.png" style="height: 12px;" /&gt; is strictly smaller than
&lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/5c2dd944dde9e08881bef0894fe7b22a5c9c4b06.svg" style="height: 16px;" type="image/svg+xml"&gt;j&lt;/object&gt;. That is, for &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/2091fb295870e9f79b6d8a10d0f6046b091e6fe5.svg" style="height: 12px;" type="image/svg+xml"&gt;n=2&lt;/object&gt;, the full product is
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/c42a249f6023c731a0d670240a66413cd79aee91.svg" style="height: 19px;" type="image/svg+xml"&gt;(x_2-x_1)(x_2-x_0)(x_1-x_0)&lt;/object&gt;. For an arbitrary &lt;img alt="n" class="valign-0" src="https://eli.thegreenplace.net/images/math/d1854cae891ec7b29161ccaf79a24b00c274bdaa.png" style="height: 8px;" /&gt;,
there are &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/66a19249b26d808e85ea349b8b84dee8a2090e0c.svg" style="height: 25px;" type="image/svg+xml"&gt;\frac{n(n-1)}{2}&lt;/object&gt; factors in total.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Math"></category></entry><entry><title>Notes on Linear Algebra for Polynomials</title><link href="https://eli.thegreenplace.net/2026/notes-on-linear-algebra-for-polynomials/" rel="alternate"></link><published>2026-02-25T18:34:00-08:00</published><updated>2026-02-26T02:33:50-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-02-25:/2026/notes-on-linear-algebra-for-polynomials/</id><summary type="html">&lt;p&gt;We’ll be working with the set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;, real polynomials
of degree &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/189de1175926fa11245f32e4d48aa2a7ab2435b4.svg" style="height: 15px;" type="image/svg+xml"&gt;\leq n&lt;/object&gt;. Such polynomials can be expressed using
&lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; scalar coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1ba9b59bdee92f38c1698c784b67ba70f803331d.svg" style="height: 11px;" type="image/svg+xml"&gt;a_i&lt;/object&gt; as follows:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/dedb05af255bc66694c47ba554c5366bdc3b2e12.svg" style="height: 22px;" type="image/svg+xml"&gt;\[p(x)=a_0+a_1 x + a_2 x^2 + \cdots + a_n x^n\]&lt;/object&gt;
&lt;div class="section" id="vector-space"&gt;
&lt;h2&gt;Vector space&lt;/h2&gt;
&lt;p&gt;The set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;, along with …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;We’ll be working with the set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;, real polynomials
of degree &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/189de1175926fa11245f32e4d48aa2a7ab2435b4.svg" style="height: 15px;" type="image/svg+xml"&gt;\leq n&lt;/object&gt;. Such polynomials can be expressed using
&lt;object class="valign-m2" data="https://eli.thegreenplace.net/images/math/db2a943efe93404e43f6ecbec79e0a4fe81b1649.svg" style="height: 14px;" type="image/svg+xml"&gt;n+1&lt;/object&gt; scalar coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1ba9b59bdee92f38c1698c784b67ba70f803331d.svg" style="height: 11px;" type="image/svg+xml"&gt;a_i&lt;/object&gt; as follows:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/dedb05af255bc66694c47ba554c5366bdc3b2e12.svg" style="height: 22px;" type="image/svg+xml"&gt;\[p(x)=a_0+a_1 x + a_2 x^2 + \cdots + a_n x^n\]&lt;/object&gt;
&lt;div class="section" id="vector-space"&gt;
&lt;h2&gt;Vector space&lt;/h2&gt;
&lt;p&gt;The set &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;, along with addition of polynomials and
scalar multiplication form a &lt;em&gt;vector space&lt;/em&gt;. As a proof, let’s review
how the vector space axioms are satisfied. We’ll use &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;,
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/90425caaec1646540a7a9049146bf2606d9dbd0d.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)&lt;/object&gt; and &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/9468a2656a6201bfa194ec81fb0f78352c9666c9.svg" style="height: 19px;" type="image/svg+xml"&gt;r(x)&lt;/object&gt; as arbitrary polynomials from the set
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; for the demonstration. Similarly, &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8.svg" style="height: 8px;" type="image/svg+xml"&gt;a&lt;/object&gt; and
&lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.svg" style="height: 13px;" type="image/svg+xml"&gt;b&lt;/object&gt; are arbitrary scalars in &lt;img alt="\mathbb{R}" class="valign-0" src="https://eli.thegreenplace.net/images/math/0ed839b111fe0e3ca2b2f618b940893eaea88a57.png" style="height: 12px;" /&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Associativity of vector addition&lt;/strong&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/45656c48313e3e3566577fdf014d18f31080ed42.svg" style="height: 19px;" type="image/svg+xml"&gt;\[p(x)+[q(x)+r(x)]=p(x)+q(x)+r(x)=[p(x)+q(x)]+r(x)\]&lt;/object&gt;
&lt;p&gt;This is trivial because addition of polynomials is associative  &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;.
Commutativity is similarly trivial, for the same reason:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Commutativity of vector addition&lt;/strong&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/4e72ebe69a19da51a0e7319eaa997c671024d3c6.svg" style="height: 19px;" type="image/svg+xml"&gt;\[p(x)+q(x)=q(x)+p(x)\]&lt;/object&gt;
&lt;p&gt;&lt;strong&gt;Identity element of vector addition&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;The zero polynomial 0 serves as an identity element.
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/1910380c6f610451862989641479600496131408.svg" style="height: 19px;" type="image/svg+xml"&gt;\forall p(x)\in P_n(\mathbb{R})&lt;/object&gt;, we have
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/d5aec4c833b53c3aee94eed8b9d7c6bd8bb577d9.svg" style="height: 19px;" type="image/svg+xml"&gt;0 + p(x) = p(x)&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inverse element of vector addition&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;For each &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;, we can use &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/eaa3a7a017152661a9e148063a189da51e966773.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)=-p(x)&lt;/object&gt; as the additive
inverse, because &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/a178e59633deb255cbecbdaecb1980ee53ec94c7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)+q(x)=0&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Identity element of scalar multiplication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The scalar 1 serves as an identity element for scalar multiplication.
For each &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;, it’s true that &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/5ba95500b0306ee6283d5d71d1a6aa41c2c0b35b.svg" style="height: 19px;" type="image/svg+xml"&gt;1\cdot p(x)=p(x)&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Associativity of scalar multiplication&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;For any two scalars &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8.svg" style="height: 8px;" type="image/svg+xml"&gt;a&lt;/object&gt; and &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.svg" style="height: 13px;" type="image/svg+xml"&gt;b&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/bc050a41a2584e826e96409fb19fda0347fa9b69.svg" style="height: 19px;" type="image/svg+xml"&gt;\[a[b\cdot p(x)]=ab\cdot p(x)=[ab]\cdot p(x)\]&lt;/object&gt;
&lt;p&gt;&lt;strong&gt;Distributivity of scalar multiplication over vector addition&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;For any &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;, &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/90425caaec1646540a7a9049146bf2606d9dbd0d.svg" style="height: 19px;" type="image/svg+xml"&gt;q(x)&lt;/object&gt; and scalar &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8.svg" style="height: 8px;" type="image/svg+xml"&gt;a&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/5e0141340db285d60c642b145119b3f279e77518.svg" style="height: 19px;" type="image/svg+xml"&gt;\[a\cdot[p(x)+q(x)]=a\cdot p(x)+a\cdot q(x)\]&lt;/object&gt;
&lt;p&gt;&lt;strong&gt;Distributivity of scalar multiplication over scalar addition&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;For any scalars &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8.svg" style="height: 8px;" type="image/svg+xml"&gt;a&lt;/object&gt; and &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.svg" style="height: 13px;" type="image/svg+xml"&gt;b&lt;/object&gt; and polynomial &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/29085125ec39218fe745bc4bf6f2aac269a42442.svg" style="height: 19px;" type="image/svg+xml"&gt;\[[a+b]\cdot p(x)=a\cdot p(x) + b\cdot p(x)\]&lt;/object&gt;
&lt;/div&gt;
&lt;div class="section" id="linear-independence-span-and-basis"&gt;
&lt;h2&gt;Linear independence, span and basis&lt;/h2&gt;
&lt;p&gt;Since we’ve shown that polynomials in &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; form a
vector space, we can now build additional linear algebraic definitions
on top of that.&lt;/p&gt;
&lt;p&gt;A set of &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/13fbd79c3d390e5d6585a21e11ff5ec1970cff0c.svg" style="height: 12px;" type="image/svg+xml"&gt;k&lt;/object&gt; polynomials &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/5375fa35be4aee1860db8d7f943aa13b9a6983c2.svg" style="height: 19px;" type="image/svg+xml"&gt;p_k(x)\in P_n(\mathbb{R})&lt;/object&gt; is said
to be &lt;em&gt;linearly independent&lt;/em&gt; if&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/11f5abb0d3349e337653153e9932b683f33cf5f3.svg" style="height: 53px;" type="image/svg+xml"&gt;\[\sum_{i=1}^{k}a_i p_i(x)=0\]&lt;/object&gt;
&lt;p&gt;implies &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/936812b8cbbba06fa5948bf8f9393e0bd9abc223.svg" style="height: 15px;" type="image/svg+xml"&gt;a_i=0 \quad \forall i&lt;/object&gt;. In words, the only linear
combination resulting in the zero vector is when all coefficients are 0.&lt;/p&gt;
&lt;p&gt;As an example, let’s discuss the fundamental building blocks of
polynomials in &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;: the set
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/dd30851f93c58a2397e24da06afb6c865f1fd4d6.svg" style="height: 20px;" type="image/svg+xml"&gt;\{1, x, x^2, \dots x^n\}&lt;/object&gt;. These are linearly independent
because:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9a1a0398b10978c0bb81a5ee5e9392e12c3fb031.svg" style="height: 19px;" type="image/svg+xml"&gt;\[a_0 + a_1 x + a_2 x^2 + \cdots a_n x^n=0\]&lt;/object&gt;
&lt;p&gt;is true only for zero polynomial, in which all the coefficients
&lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/fc7c49cdcf0b6f4f244a8e180fc8df4513e6a42e.svg" style="height: 15px;" type="image/svg+xml"&gt;a_i=0&lt;/object&gt;. This comes from the very definition of polynomials.
Moreover, this set &lt;em&gt;spans&lt;/em&gt; the entire &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; because
every polynomial can be (by definition) expressed as a linear combination of
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/dd30851f93c58a2397e24da06afb6c865f1fd4d6.svg" style="height: 20px;" type="image/svg+xml"&gt;\{1, x, x^2, \dots x^n\}&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;Since we’ve shown these basic polynomials are linearly independent and
span the entire vector space, they are a &lt;em&gt;basis&lt;/em&gt; for the space. In fact,
this set has a special name: the &lt;em&gt;monomial basis&lt;/em&gt; (because a monomial is
a polynomial with a single term).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="checking-if-an-arbitrary-set-of-polynomials-is-a-basis"&gt;
&lt;h2&gt;Checking if an arbitrary set of polynomials is a basis&lt;/h2&gt;
&lt;p&gt;Suppose we have some set polynomials, and we want to know if these form
a basis for &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;. How do we go about it?&lt;/p&gt;
&lt;p&gt;The idea is using linear algebra the same way we do for any other vector
space. Let’s use a concrete example to demonstrate:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/0a1ff12f973cf607bf879deb912333051a671de5.svg" style="height: 22px;" type="image/svg+xml"&gt;\[Q=\{1-x, x, 2x+x^2\}\]&lt;/object&gt;
&lt;p&gt;Is the set &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/c3156e00d3c2588c639e0d3cf6821258b05761c7.svg" style="height: 16px;" type="image/svg+xml"&gt;Q&lt;/object&gt; a basis for &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;? We’ll start by
checking whether the members of &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/c3156e00d3c2588c639e0d3cf6821258b05761c7.svg" style="height: 16px;" type="image/svg+xml"&gt;Q&lt;/object&gt; are linearly independent.
Write:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/41bdcc040d729f93edeb130b51a9ac3ee66b72d5.svg" style="height: 22px;" type="image/svg+xml"&gt;\[a_0(1-x)+a_1 x + a_2(2x+x^2)=0\]&lt;/object&gt;
&lt;p&gt;By regrouping, we can turn this into:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/73036a5532059c810bb37bd3a8dc1abdb85c9b1f.svg" style="height: 22px;" type="image/svg+xml"&gt;\[a_0 + (a_1-a_0+2a_2)x+a_2 x^2=0\]&lt;/object&gt;
&lt;p&gt;For this to be true, the coefficient of each monomial has to be zero;
mathematically:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/4e5d5010eec23b675caf4b1936082d48a0b9ec1f.svg" style="height: 65px;" type="image/svg+xml"&gt;\[\begin{aligned}
    a_0&amp;amp;=0\\
    a_1-a_0+2a_2&amp;amp;=0\\
    a2&amp;amp;=0\\
\end{aligned}\]&lt;/object&gt;
&lt;p&gt;In matrix form:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/78c214c04a41b196844997d84ceeb8df0e5d76e2.svg" style="height: 64px;" type="image/svg+xml"&gt;\[\begin{bmatrix}
    1 &amp;amp; 0 &amp;amp; 0\\
    -1 &amp;amp; 1 &amp;amp; 2\\
    0 &amp;amp; 0 &amp;amp; 1\\
\end{bmatrix}
\begin{bmatrix}a_0\\ a_1\\ a_2\end{bmatrix}
=\begin{bmatrix}0\\ 0\\ 0\end{bmatrix}\]&lt;/object&gt;
&lt;p&gt;We know how to solve this, by reducing the matrix into &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Row_echelon_form"&gt;row-echelon
form&lt;/a&gt;. It’s easy to
see that the reduced row-echelon form of this specific matrix is
&lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/ca73ab65568cd125c2d27a22bbd9e863c10b675d.svg" style="height: 12px;" type="image/svg+xml"&gt;I&lt;/object&gt;, the identity matrix. Therefore, this set of equations has a
single solution: &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/936812b8cbbba06fa5948bf8f9393e0bd9abc223.svg" style="height: 15px;" type="image/svg+xml"&gt;a_i=0 \quad \forall i&lt;/object&gt;  &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ve shown that the set &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/c3156e00d3c2588c639e0d3cf6821258b05761c7.svg" style="height: 16px;" type="image/svg+xml"&gt;Q&lt;/object&gt; is linearly independent. Now let’s
show that it &lt;em&gt;spans&lt;/em&gt; the space &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;. We want to
analyze:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/4ddca074738e89bc063a29a7cd67215ada8fffe5.svg" style="height: 22px;" type="image/svg+xml"&gt;\[a_0(1-x)+a_1 x + a_2(2x+x^2)=\alpha +\beta x + \gamma x^2\]&lt;/object&gt;
&lt;p&gt;And find the coefficients &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1ba9b59bdee92f38c1698c784b67ba70f803331d.svg" style="height: 11px;" type="image/svg+xml"&gt;a_i&lt;/object&gt; that satisfy this for any
arbitrary &lt;img alt="\alpha" class="valign-0" src="https://eli.thegreenplace.net/images/math/f7c665b45932a814215e979bc2611080b4948e68.png" style="height: 8px;" /&gt;, &lt;img alt="\beta" class="valign-m4" src="https://eli.thegreenplace.net/images/math/6499d503bfc00cadae1440b191c52a8632e2f8c4.png" style="height: 16px;" /&gt; and &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/67833ee2012ec1c6254b6c009dc72bf0dc48aa6d.svg" style="height: 12px;" type="image/svg+xml"&gt;\gamma&lt;/object&gt;. We proceed
just as before, by regrouping on the left side:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/7bcb2f196e39a4b859cddf4123ee02481940fafa.svg" style="height: 22px;" type="image/svg+xml"&gt;\[a_0 + (a_1-a_0+2a_2)x+a_2 x^2=\alpha +\beta x + \gamma x^2\]&lt;/object&gt;
&lt;p&gt;and equating the coefficient of each power of &lt;img alt="x" class="valign-0" src="https://eli.thegreenplace.net/images/math/11f6ad8ec52a2984abaafd7c3b516503785c2072.png" style="height: 8px;" /&gt; separately:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/f32a037534eba0f569a01b0d2e763b9ff3c54587.svg" style="height: 65px;" type="image/svg+xml"&gt;\[\begin{aligned}
    a_0&amp;amp;=\alpha\\
    a_1-a_0+2a_2&amp;amp;=\beta\\
    a2&amp;amp;=\gamma\\
\end{aligned}\]&lt;/object&gt;
&lt;p&gt;If we turn this into matrix form, the matrix of coefficients is exactly
the same as before. So we know there’s a single solution, and by
rearranging the matrix into &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/ca73ab65568cd125c2d27a22bbd9e863c10b675d.svg" style="height: 12px;" type="image/svg+xml"&gt;I&lt;/object&gt;, the solution will appear on the
right hand side. It doesn’t matter for the moment what the actual
solution is, as long as it exists and is unique. We’ve shown that
&lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/c3156e00d3c2588c639e0d3cf6821258b05761c7.svg" style="height: 16px;" type="image/svg+xml"&gt;Q&lt;/object&gt; spans the space!&lt;/p&gt;
&lt;p&gt;Since the set &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/c3156e00d3c2588c639e0d3cf6821258b05761c7.svg" style="height: 16px;" type="image/svg+xml"&gt;Q&lt;/object&gt; is linearly independent and spans
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;, it is a &lt;em&gt;basis&lt;/em&gt; for the space.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="inner-product"&gt;
&lt;h2&gt;Inner product&lt;/h2&gt;
&lt;p&gt;I’ve discussed inner products for functions in &lt;a class="reference external" href="https://eli.thegreenplace.net/2025/hilbert-space-treating-functions-as-vectors/"&gt;the post about Hilbert
space&lt;/a&gt;.
Well, &lt;em&gt;polynomials are functions&lt;/em&gt;, so we can define an inner product
using integrals as follows &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/a7073ffb2e322d7c7b9118b0fcc517c0c484fcb3.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\langle p, q \rangle = \int_{a}^{b} p(x) q(x) w(x) \, dx\]&lt;/object&gt;
&lt;p&gt;Where the bounds &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8.svg" style="height: 8px;" type="image/svg+xml"&gt;a&lt;/object&gt; and &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98.svg" style="height: 13px;" type="image/svg+xml"&gt;b&lt;/object&gt; are arbitrary, and could be
infinite. Whenever we deal with integrals we worry about convergence; in
my post on Hilbert spaces, we only talked about &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/f1aae083af2c79348dd78712847ebd55537fa6e6.svg" style="height: 15px;" type="image/svg+xml"&gt;L^2&lt;/object&gt; - the square
integrable functions. Most polynomials are not square integrable,
however. Therefore, we can restrict this using either:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;A special &lt;em&gt;weight function&lt;/em&gt; &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/02e80b7cfa178127b4c5282079352105a2c55cf7.svg" style="height: 19px;" type="image/svg+xml"&gt;w(x)&lt;/object&gt; to make sure the inner
product integral converges&lt;/li&gt;
&lt;li&gt;Set finite bounds on the integral, and then we can just set
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/62c32bb13769e79e5a65343da35baaa514b873e3.svg" style="height: 19px;" type="image/svg+xml"&gt;w(x)=1&lt;/object&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s use the latter, and restrict the bounds into the range
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/d5610f38fe75d8d90ac09fd335bad6823492589d.svg" style="height: 18px;" type="image/svg+xml"&gt;[-1,1]&lt;/object&gt;, setting &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/62c32bb13769e79e5a65343da35baaa514b873e3.svg" style="height: 19px;" type="image/svg+xml"&gt;w(x)=1&lt;/object&gt;. We have the following inner
product:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/9f9575d1c81856a7fd8b8b71352a33119fe023e3.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\langle p, q \rangle = \int_{-1}^{1} p(x) q(x) \, dx\]&lt;/object&gt;
&lt;p&gt;Let’s check that this satisfies the inner product space conditions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conjugate symmetry&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Since real multiplication is commutative, we can write:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/e5584cd03887f39cb8b7c8cbec3ca64a1d4347da.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\langle p, q \rangle = \int_{-1}^{1} p(x) q(x) \, dx =\int_{-1}^{1} q(x) p(x) \, dx=\langle q, p \rangle\]&lt;/object&gt;
&lt;p&gt;We deal in the reals here, so we can safely ignore complex conjugation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linearity in the first argument&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Let &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/c9b738cad43aea3a80a02e1f075a09868eb73e26.svg" style="height: 19px;" type="image/svg+xml"&gt;p_1,p_2,q\in P_n(\mathbb{R})&lt;/object&gt; and &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/44a2f2358e9ec13e88b5a266ee1bd804a43c5864.svg" style="height: 16px;" type="image/svg+xml"&gt;a,b\in \mathbb{R}&lt;/object&gt;.
We want to show that&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/d25bedb6d14cf20fbf28b583412dfd9ac14f6cf3.svg" style="height: 19px;" type="image/svg+xml"&gt;\[\langle ap_1+bp_2,q \rangle = a\langle p_1,q\rangle +b\langle p_2,q\rangle\]&lt;/object&gt;
&lt;p&gt;Expand the left-hand side using our definition of inner product:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/3549d9fc0999c0fadd0458ece4609f5b08780052.svg" style="height: 95px;" type="image/svg+xml"&gt;\[\begin{aligned}
    \langle ap_1+bp_2,q \rangle&amp;amp;=\int_{-1}^{1} (a p_1(x)+b p_2(x)) q(x) \, dx\\
    &amp;amp;=a\int_{-1}^{1} p_1(x) q(x) \, dx+b\int_{-1}^{1} p_2(x) q(x) \, dx
\end{aligned}\]&lt;/object&gt;
&lt;p&gt;The result is equivalent to
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7239344e074d1e86c42e73c5470999705411aacc.svg" style="height: 19px;" type="image/svg+xml"&gt;a\langle p_1,q\rangle +b\langle p_2,q\rangle&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Positive-definiteness&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;We want to show that for nonzero &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/8a22a4ced0990db5e8d521832fddacd240fc599f.svg" style="height: 19px;" type="image/svg+xml"&gt;p\in P_n(\mathbb{R})&lt;/object&gt;, we have
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/c8d11b2d1c5d8a6529b6068917407194be2a7b2d.svg" style="height: 19px;" type="image/svg+xml"&gt;\langle p, p\rangle &amp;gt; 0&lt;/object&gt;. First of all, since &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/5e91383f4084e35136e78c00570cf313965465e9.svg" style="height: 20px;" type="image/svg+xml"&gt;p(x)^2\geq0&lt;/object&gt;
for all &lt;img alt="x" class="valign-0" src="https://eli.thegreenplace.net/images/math/11f6ad8ec52a2984abaafd7c3b516503785c2072.png" style="height: 8px;" /&gt;, it’s true that:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/44dd56558739e571dee1200a50039374e485e05b.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\langle p, p\rangle=\int_{-1}^{1}p(x)^2\, dx\geq 0\]&lt;/object&gt;
&lt;p&gt;What about the result 0 though? Well, let’s say that&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/31b9ef374de82013e825fc205670d3ebe0dfc541.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\int_{-1}^{1}p(x)^2\, dx=0\]&lt;/object&gt;
&lt;p&gt;Since &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/11bf1f75edece9993925ac94a0a1f158762c2111.svg" style="height: 20px;" type="image/svg+xml"&gt;p(x)^2&lt;/object&gt; is a non-negative function, this means that the
integral of a non-negative function ends up being 0. But &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/7f86e6c6bb632c1ca2518f269fc1cc1b6737d4f7.svg" style="height: 19px;" type="image/svg+xml"&gt;p(x)&lt;/object&gt; is
a polynomial, so it’s &lt;em&gt;continuous&lt;/em&gt;, and so is &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/11bf1f75edece9993925ac94a0a1f158762c2111.svg" style="height: 20px;" type="image/svg+xml"&gt;p(x)^2&lt;/object&gt;. If the
integral of a continuous non-negative function is 0, it means the
function itself is 0. Had it been non-zero in any place, the integral
would necessarily have to be positive as well.&lt;/p&gt;
&lt;p&gt;We’ve proven that &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/9de22135888f7085d51d8b1cbd987d2c68a050ec.svg" style="height: 19px;" type="image/svg+xml"&gt;\langle p, p\rangle=0&lt;/object&gt; only when &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/516b9783fca517eecbd1d064da2d165310b19759.svg" style="height: 12px;" type="image/svg+xml"&gt;p&lt;/object&gt; is
the zero polynomial. The positive-definiteness condition is satisfied.&lt;/p&gt;
&lt;p&gt;In conclusion, &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; along with the inner product
we’ve defined forms an &lt;em&gt;inner product space&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="orthogonality"&gt;
&lt;h2&gt;Orthogonality&lt;/h2&gt;
&lt;p&gt;Now that we have an inner product, we can define orthogonality on
polynomials: two polynomials &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/d2926c1571d4e1b8561077d3308b7cfb6d211e59.svg" style="height: 12px;" type="image/svg+xml"&gt;p,q&lt;/object&gt; are &lt;em&gt;orthogonal&lt;/em&gt; (w.r.t. our
inner product) iff&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/b994221c38da6d42afb973d4318ac59c4940f545.svg" style="height: 44px;" type="image/svg+xml"&gt;\[\langle p,q\rangle=\int_{-1}^{1}p(x)q(x)\, dx=0\]&lt;/object&gt;
&lt;p&gt;Contrary to expectation  &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt;, the monomial basis polynomials are &lt;em&gt;not&lt;/em&gt;
orthogonal using our definition of inner product.&lt;/p&gt;
&lt;p&gt;For example, calculating the inner product for &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/356a192b7913b04c54574d18c28d46e6395428ab.svg" style="height: 12px;" type="image/svg+xml"&gt;1&lt;/object&gt; and
&lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/7046d961a8144b7b2c2da6066849a9f889ff2ac9.svg" style="height: 15px;" type="image/svg+xml"&gt;x^2&lt;/object&gt;:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/c6937eec30d2a69a76efdbd489e70d3ad18c0c29.svg" style="height: 47px;" type="image/svg+xml"&gt;\[\langle 1,x^2\rangle=\int_{-1}^{1}x^2\, dx=\frac{x^3}{3}\biggr|_{-1}^{1}=\frac{2}{3}\]&lt;/object&gt;
&lt;p&gt;There are other sets of polynomials that &lt;em&gt;are&lt;/em&gt; orthogonal using our
inner product. For example, the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Legendre_polynomials"&gt;Legendre
polynomials&lt;/a&gt;; but
this is a topic for another post.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;There’s a level of basic algebra below which we won’t descend in
these notes. We could break this statement further down by saying
that something like &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/bdcf820e656d6ef78cf1729b58556a6a9e2e9fd4.svg" style="height: 21px;" type="image/svg+xml"&gt;a_i x^i + a_j x^j&lt;/object&gt; can be added to
&lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/e2d0aea0ffca9c5c9870d56c7f0247349f759f49.svg" style="height: 21px;" type="image/svg+xml"&gt;b_i x^i + b_j x^j&lt;/object&gt; by adding each power of &lt;img alt="x" class="valign-0" src="https://eli.thegreenplace.net/images/math/11f6ad8ec52a2984abaafd7c3b516503785c2072.png" style="height: 8px;" /&gt;
separately for any &lt;img alt="i" class="valign-0" src="https://eli.thegreenplace.net/images/math/042dc4512fa3d391c5170cf3aa61e6a638f84342.png" style="height: 12px;" /&gt; and &lt;object class="valign-m4" data="https://eli.thegreenplace.net/images/math/5c2dd944dde9e08881bef0894fe7b22a5c9c4b06.svg" style="height: 16px;" type="image/svg+xml"&gt;j&lt;/object&gt;, but let’s just take it
for granted.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Obviously, this specific set of equations is quite trivial to solve
without matrices; I just want to demonstrate the more general
approach. Once we have a system of linear equations, the whole
toolbox of linear algebra is at our disposal. For example, we could
also have checked the determinant and seen it’s non-zero, which means
that a square matrix is invertible, and in this case has a single
solution of zeroes.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;And actually with this (or any valid) inner product,
&lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt; indeed forms a Hilbert space, because it’s
finite-dimensional, and every finite-dimensional inner product space
is complete.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Because of how naturally this set spans &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/66e617fc4a3781fe03dcf20effb656feaa81a47e.svg" style="height: 19px;" type="image/svg+xml"&gt;P_n(\mathbb{R})&lt;/object&gt;. And
indeed, we can define alternative inner products using which
monomials are orthogonal.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Math"></category></entry><entry><title>Rewriting pycparser with the help of an LLM</title><link href="https://eli.thegreenplace.net/2026/rewriting-pycparser-with-the-help-of-an-llm/" rel="alternate"></link><published>2026-02-04T19:35:00-08:00</published><updated>2026-02-05T03:38:39-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-02-04:/2026/rewriting-pycparser-with-the-help-of-an-llm/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/eliben/pycparser"&gt;pycparser&lt;/a&gt; is my most widely used open
source project (with ~20M daily downloads from PyPI &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;). It's a pure-Python
parser for the C programming language, producing ASTs inspired by &lt;a class="reference external" href="https://docs.python.org/3/library/ast.html"&gt;Python's
own&lt;/a&gt;. Until very recently, it's
been using &lt;a class="reference external" href="https://www.dabeaz.com/ply/ply.html"&gt;PLY: Python Lex-Yacc&lt;/a&gt; for
the core parsing.&lt;/p&gt;
&lt;p&gt;In this post, I'll describe how …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/eliben/pycparser"&gt;pycparser&lt;/a&gt; is my most widely used open
source project (with ~20M daily downloads from PyPI &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;). It's a pure-Python
parser for the C programming language, producing ASTs inspired by &lt;a class="reference external" href="https://docs.python.org/3/library/ast.html"&gt;Python's
own&lt;/a&gt;. Until very recently, it's
been using &lt;a class="reference external" href="https://www.dabeaz.com/ply/ply.html"&gt;PLY: Python Lex-Yacc&lt;/a&gt; for
the core parsing.&lt;/p&gt;
&lt;p&gt;In this post, I'll describe how I collaborated with an LLM coding agent (Codex)
to help me rewrite pycparser to use a hand-written recursive-descent parser and
remove the dependency on PLY. This has been an interesting experience and the
post contains lots of information and is therefore quite long; if you're just
interested in the final result, check out the latest code of pycparser - the
&lt;tt class="docutils literal"&gt;main&lt;/tt&gt; branch already has the new implementation.&lt;/p&gt;
&lt;img alt="meme picture saying &amp;quot;can't come to bed because my AI agent produced something slightly wrong&amp;quot;" class="align-center" src="https://eli.thegreenplace.net/images/2026/cantcometobed.png" /&gt;
&lt;div class="section" id="the-issues-with-the-existing-parser-implementation"&gt;
&lt;h2&gt;The issues with the existing parser implementation&lt;/h2&gt;
&lt;p&gt;While pycparser has been working well overall, there were a number of nagging
issues that persisted over years.&lt;/p&gt;
&lt;div class="section" id="parsing-strategy-yacc-vs-hand-written-recursive-descent"&gt;
&lt;h3&gt;Parsing strategy: YACC vs. hand-written recursive descent&lt;/h3&gt;
&lt;p&gt;I began working on pycparser in 2008, and back then using a YACC-based approach
for parsing a whole language like C seemed like a no-brainer to me. Isn't this
what everyone does when writing a serious parser? Besides, the K&amp;amp;R2 book
famously carries the entire grammar of the C99 language in an appendix - so it
seemed like a simple matter of translating that to PLY-yacc syntax.&lt;/p&gt;
&lt;p&gt;And indeed, it wasn't &lt;em&gt;too&lt;/em&gt; hard, though there definitely were some complications
in building the ASTs for declarations (C's &lt;a class="reference external" href="https://eli.thegreenplace.net/2008/10/18/implementing-cdecl-with-pycparser"&gt;gnarliest part&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Shortly after completing pycparser, I got more and more interested in compilation
and started learning about the different kinds of parsers more seriously. Over
time, I grew convinced that &lt;a class="reference external" href="https://eli.thegreenplace.net/tag/recursive-descent-parsing"&gt;recursive descent&lt;/a&gt; is the way to
go - producing parsers that are easier to understand and maintain (and are often
faster!).&lt;/p&gt;
&lt;p&gt;It all ties in to the &lt;a class="reference external" href="https://eli.thegreenplace.net/2017/benefits-of-dependencies-in-software-projects-as-a-function-of-effort/"&gt;benefits of dependencies in software projects as a
function of effort&lt;/a&gt;.
Using parser generators is a heavy &lt;em&gt;conceptual&lt;/em&gt; dependency: it's really nice
when you have to churn out many parsers for small languages. But when you have
to maintain a single, very complex parser, as part of a large project - the
benefits quickly dissipate and you're left with a substantial dependency that
you constantly grapple with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-other-issue-with-dependencies"&gt;
&lt;h3&gt;The other issue with dependencies&lt;/h3&gt;
&lt;p&gt;And then there are the usual problems with dependencies; dependencies get
abandoned, and they may also develop security issues. Sometimes, both of these
become true.&lt;/p&gt;
&lt;p&gt;Many years ago, pycparser forked and started vendoring its own version of PLY.
This was part of transitioning pycparser to a dual Python 2/3 code base when PLY
was slower to adapt. I believe this was the right decision, since PLY &amp;quot;just
worked&amp;quot; and I didn't have to deal with active (and very tedious in the Python
ecosystem, where packaging tools are replaced faster than dirty socks)
dependency management.&lt;/p&gt;
&lt;p&gt;A couple of weeks ago &lt;a class="reference external" href="https://github.com/eliben/pycparser/issues/588"&gt;this issue&lt;/a&gt;
was opened for pycparser. It turns out the some old PLY code triggers security
checks used by some Linux distributions; while this code was fixed in a later
commit of PLY, PLY itself was apparently abandoned and archived in late 2025.
And guess what? That happened in the middle of a large rewrite of the package,
so re-vendoring the pre-archiving commit seemed like a risky proposition.&lt;/p&gt;
&lt;p&gt;On the issue it was suggested that &amp;quot;hopefully the dependent packages move on to
a non-abandoned parser or implement their own&amp;quot;; I originally laughed this idea
off, but then it got me thinking... which is what this post is all about.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="growing-complexity-of-parsing-a-messy-language"&gt;
&lt;h3&gt;Growing complexity of parsing a messy language&lt;/h3&gt;
&lt;p&gt;The original K&amp;amp;R2 grammar for C99 had - famously - a single shift-reduce
conflict having to do with dangling &lt;tt class="docutils literal"&gt;else&lt;/tt&gt;s belonging to the most recent
&lt;tt class="docutils literal"&gt;if&lt;/tt&gt; statement. And indeed, other than the famous &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lexer_hack"&gt;lexer hack&lt;/a&gt;
used to deal with &lt;a class="reference external" href="https://eli.thegreenplace.net/2011/05/02/the-context-sensitivity-of-cs-grammar-revisited"&gt;C's type name / ID ambiguity&lt;/a&gt;,
pycparser only had this single shift-reduce conflict.&lt;/p&gt;
&lt;p&gt;But things got more complicated. Over the years, features were added that
weren't strictly in the standard but were supported by all the industrial
compilers. The more advanced C11 and C23 standards weren't beholden to the
promises of conflict-free YACC parsing (since almost no industrial-strength
compilers use YACC at this point), so all caution went out of the window.&lt;/p&gt;
&lt;p&gt;The latest (PLY-based) release of pycparser has many reduce-reduce conflicts
&lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;; these are a severe maintenance hazard because it means the parsing rules
essentially have to be tie-broken by order of appearance in the code. This is
very brittle; pycparser has only managed to maintain its stability and quality
through its comprehensive test suite. Over time, it became harder and harder to
extend, because YACC parsing rules have all kinds of spooky-action-at-a-distance
effects. The straw that broke the camel's back was &lt;a class="reference external" href="https://github.com/eliben/pycparser/pull/590"&gt;this PR&lt;/a&gt; which again proposed to
increase the number of reduce-reduce conflicts &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This - again - prompted me to think &amp;quot;what if I just dump YACC and switch to
a hand-written recursive descent parser&amp;quot;, and here we are.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-mental-roadblock"&gt;
&lt;h2&gt;The mental roadblock&lt;/h2&gt;
&lt;p&gt;None of the challenges described above are new; I've been pondering them for
many years now, and yet biting the bullet and rewriting the parser didn't feel
like something I'd like to get into. By my private estimates it'd take at least
a week of deep heads-down work to port the gritty 2000 lines of YACC grammar
rules to a recursive descent parser &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt;. Moreover, it wouldn't be a
particularly &lt;em&gt;fun&lt;/em&gt; project either - I didn't feel like I'd learn much new and
my interests have shifted away from this project. In short, the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Potential_well"&gt;Potential well&lt;/a&gt; was just too deep.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="why-would-this-even-work-tests"&gt;
&lt;h2&gt;Why would this even work? Tests&lt;/h2&gt;
&lt;p&gt;I've definitely noticed the improvement in capabilities of LLM coding
agents in the past few months, and many reputable people online rave about using
them for increasingly larger projects. That said, would an LLM agent really be
able to accomplish such a complex project on its own? This isn't just a toy,
it's thousands of lines of dense parsing code.&lt;/p&gt;
&lt;p&gt;What gave me hope is the concept of &lt;a class="reference external" href="https://simonwillison.net/2025/Dec/31/the-year-in-llms/#the-year-of-conformance-suites"&gt;conformance suites mentioned by
Simon Willison&lt;/a&gt;.
Agents seem to do well when there's a very clear and rigid
goal function - such as a large, high-coverage conformance test suite.&lt;/p&gt;
&lt;p&gt;And pycparser has an &lt;a class="reference external" href="https://github.com/eliben/pycparser/blob/main/tests/test_c_parser.py"&gt;very extensive one&lt;/a&gt;.
Over 2500 lines of test code parsing various C snippets to ASTs with expected
results, grown over a decade and a half of real issues and bugs reported by
users.&lt;/p&gt;
&lt;p&gt;I figured the LLM can either succeed or fail and throw its hands up in despair,
but it's quite unlikely to produce a &lt;em&gt;wrong&lt;/em&gt; port that would still pass all
the tests. So I set it to run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-initial-port"&gt;
&lt;h2&gt;The initial port&lt;/h2&gt;
&lt;p&gt;I fired up Codex in pycparser's repository, and wrote this prompt just to
make sure it understands me and can run the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;This repository implements a Python parser producing an AST. It has a very
extensive test suite; can you first verify you successfully run all the tests?
(with python3 -m unittest discover, from the root dir)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Codex figured it out (I gave it the exact command, after all!); my next
prompt was the real thing &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pycparser uses the PLY library for its lexer and parser, and its own AST
package for the ASTs created by the parser. I want to explore replacing the
Parser component (leaving the Lexer alone for now) with a hand written
recursive descent parser that will pass all the same tests. Can you try that?
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here Codex went to work and churned &lt;em&gt;for over an hour&lt;/em&gt;. Having never
observed an agent work for nearly this long, I kind of assumed it went off
the rails and will fail sooner or later. So I was rather surprised and skeptical
when it eventually came back with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Replaced the PLY parse path with a handwritten recursive‑descent parser while
keeping the existing lexer and AST helpers intact; CParser now points to the
new RD implementation, with the old PLY version preserved as PLYCParser in
pycparser/c_parser.py. The RD parser includes a token stream, declarator/type
parsing, expression precedence handling, pragmas, and statement parsing to
match the previous AST output and coordinate behavior.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It took me a while to poke around the code and run it until I was convinced - it
had actually done it! It wrote a new recursive descent parser with only
ancillary dependencies on PLY, and that parser passed the test suite. After a
few more prompts, we've removed the ancillary dependencies and made the
structure clearer. I hadn't looked too deeply into code quality at this point,
but at least on the functional level - it succeeded. This was very impressive!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-quick-note-on-reviews-and-branches"&gt;
&lt;h2&gt;A quick note on reviews and branches&lt;/h2&gt;
&lt;p&gt;A change like the one described above is impossible to code-review as one PR in
any meaningful way; so I used a different strategy. Before embarking on this
path, I created a new branch and once Codex finished the initial rewrite, I
committed this change, knowing that I will review it in detail, piece-by-piece
later on.&lt;/p&gt;
&lt;p&gt;Even though coding agents have their own notion of history and can &amp;quot;revert&amp;quot;
certain changes, I felt much safer relying on Git. In the worst case if all of
this goes south, I can nuke the branch and it's as if nothing ever happened.
I was determined to only merge this branch onto &lt;tt class="docutils literal"&gt;main&lt;/tt&gt; once I was fully
satisfied with the code. In what follows, I had to &lt;tt class="docutils literal"&gt;git reset&lt;/tt&gt; several times
when I didn't like the direction in which Codex was going. In hindsight, doing
this work in a branch was absolutely the right choice.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-long-tail-of-goofs"&gt;
&lt;h2&gt;The long tail of goofs&lt;/h2&gt;
&lt;p&gt;Once I've sufficiently convinced myself that the new parser is actually working,
I used Codex to similarly rewrite the lexer and get rid of the PLY dependency
entirely, deleting it from the repository. Then, I started looking more deeply
into code quality - reading the code created by Codex and trying to wrap my head
around it.&lt;/p&gt;
&lt;p&gt;And - oh my - this was quite the journey. Much has been written about the code
produced by agents, and much of it seems to be true. Maybe it's a setting I'm
missing (I'm not using my own custom &lt;tt class="docutils literal"&gt;AGENTS.md&lt;/tt&gt; yet, for instance), but
Codex seems to be that eager programmer that wants to get from A to B whatever
the cost. Readability, minimalism and code clarity are very much secondary
goals.&lt;/p&gt;
&lt;p&gt;Using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;raise...except&lt;/span&gt;&lt;/tt&gt; for control flow? Yep. Abusing Python's weak typing
(like having &lt;tt class="docutils literal"&gt;None&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;false&lt;/tt&gt; and other values all mean different things
for a given variable)? For sure. Spreading the logic of a complex function
all over the place instead of putting all the key parts in a single switch
statement? You bet.&lt;/p&gt;
&lt;p&gt;Moreover, the agent is hilariously &lt;em&gt;lazy&lt;/em&gt;. More than once I had to convince it
to do something it initially said is impossible, and even insisted again in
follow-up messages. The anthropomorphization here is mildly concerning, to be
honest. I could never imagine I would be writing something like the following to
a computer, and yet - here we are: &amp;quot;Remember how we moved X to Y before? You
can do it again for Z, definitely. Just try&amp;quot;.&lt;/p&gt;
&lt;p&gt;My process was to see how I can instruct Codex to fix things, and intervene
myself (by rewriting code) as little as possible. I've &lt;em&gt;mostly&lt;/em&gt; succeeded in
this, and did maybe 20% of the work myself.&lt;/p&gt;
&lt;p&gt;My branch grew &lt;em&gt;dozens&lt;/em&gt; of commits, falling into roughly these categories:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The code in X is too complex; why can't we do Y instead?&lt;/li&gt;
&lt;li&gt;The use of X is needlessly convoluted; change Y to Z, and T to V in all
instances.&lt;/li&gt;
&lt;li&gt;The code in X is unclear; please add a detailed comment - with examples - to
explain what it does.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Interestingly, after doing (3), the agent was often more effective in giving
the code a &amp;quot;fresh look&amp;quot; and succeeding in either (1) or (2).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-end-result"&gt;
&lt;h2&gt;The end result&lt;/h2&gt;
&lt;p&gt;Eventually, after many hours spent in this process, I was reasonably pleased
with the code. It's far from perfect, of course, but taking the essential
complexities into account, it's something I could see myself maintaining (with
or without the help of an agent). I'm sure I'll find more ways to improve it
in the future, but I have a reasonable degree of confidence that this will be
doable.&lt;/p&gt;
&lt;p&gt;It passes all the tests, so I've been able to release a new version (3.00)
without major issues so far. The only issue I've discovered is that some of
CFFI's tests are overly precise about the phrasing of errors reported by
pycparser; this was &lt;a class="reference external" href="https://github.com/python-cffi/cffi/pull/224"&gt;an easy fix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new parser is also faster, by about 30% based on my benchmarks! This is
typical of recursive descent when compared with YACC-generated parsers, in my
experience. After reviewing the initial rewrite of the lexer, I've spent a while
instructing Codex on how to make it faster, and it worked reasonably well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="followup-static-typing"&gt;
&lt;h2&gt;Followup - static typing&lt;/h2&gt;
&lt;p&gt;While working on this, it became quite obvious that static typing would make the
process easier. LLM coding agents really benefit from closed loops with strict
guardrails (e.g. a test suite to pass), and type-annotations act as such.
For example, had pycparser already been type annotated, Codex would probably not
have overloaded values to multiple types (like &lt;tt class="docutils literal"&gt;None&lt;/tt&gt; vs. &lt;tt class="docutils literal"&gt;False&lt;/tt&gt; vs.
others).&lt;/p&gt;
&lt;p&gt;In a followup, I asked Codex to type-annotate pycparser (running checks using
&lt;tt class="docutils literal"&gt;ty&lt;/tt&gt;), and this was also a back-and-forth because the process exposed some
issues that needed to be refactored. Time will tell, but hopefully it will make
further changes in the project simpler for the agent.&lt;/p&gt;
&lt;p&gt;Based on this experience, I'd bet that coding agents will be somewhat more
effective in strongly typed languages like Go, TypeScript and especially Rust.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Overall, this project has been a really good experience, and I'm impressed with
what modern LLM coding agents can do! While there's no reason to expect that
progress in this domain will stop, even if it does - these are already very
useful tools that can significantly improve programmer productivity.&lt;/p&gt;
&lt;p&gt;Could I have done this myself, without an agent's help? Sure. But it would have
taken me &lt;em&gt;much&lt;/em&gt; longer, assuming that I could even muster the will and
concentration to engage in this project. I estimate it would take me at least
a week of full-time work (so 30-40 hours) spread over who knows how long to
accomplish. With Codex, I put in an order of magnitude less work into this
(around 4-5 hours, I'd estimate) and I'm happy with the result.&lt;/p&gt;
&lt;p&gt;It was also &lt;em&gt;fun&lt;/em&gt;. At least in one sense, my professional life can be described
as the pursuit of focus, deep work and &lt;em&gt;flow&lt;/em&gt;. It's not easy for me to get into
this state, but when I do I'm highly productive and find it very enjoyable.
Agents really help me here. When I know I need to write some code and it's
hard to get started, asking an agent to write a prototype is a great catalyst
for my motivation. Hence the meme at the beginning of the post.&lt;/p&gt;
&lt;div class="section" id="does-code-quality-even-matter"&gt;
&lt;h3&gt;Does code quality even matter?&lt;/h3&gt;
&lt;p&gt;One can't avoid a nagging question - does the quality of the code produced
by agents even matter? Clearly, the agents themselves can understand it (if not
today's agent, then at least next year's). Why worry about future
maintainability if the agent can maintain it? In other words, does it make sense
to just go full vibe-coding?&lt;/p&gt;
&lt;p&gt;This is a fair question, and one I don't have an answer to. Right now, for
projects I maintain and &lt;em&gt;stand behind&lt;/em&gt;, it seems obvious to me that the code
should be fully understandable and accepted by me, and the agent is just a tool
helping me get to that state more efficiently. It's hard to say what the future
holds here; it's going to interesting, for sure.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;pycparser has a fair number of &lt;a class="reference external" href="https://deps.dev/pypi/pycparser/3.0.0/dependents"&gt;direct dependents&lt;/a&gt;,
but the majority of downloads comes through &lt;a class="reference external" href="https://github.com/python-cffi/cffi"&gt;CFFI&lt;/a&gt;,
which itself is a major building block for much of the Python ecosystem.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The table-building report says 177, but that's certainly an
over-dramatization because it's common for a single conflict to
manifest in several ways.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;It didn't help the PR's case that it was almost certainly vibe coded.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;p class="first"&gt;There was also the lexer to consider, but this seemed like a much
simpler job. My impression is that in the early days of computing,
&lt;tt class="docutils literal"&gt;lex&lt;/tt&gt; gained prominence because of strong regexp support which wasn't
very common yet. These days, with excellent regexp libraries
existing for pretty much every language, the added value of &lt;tt class="docutils literal"&gt;lex&lt;/tt&gt; over
a &lt;a class="reference external" href="https://eli.thegreenplace.net/2013/06/25/regex-based-lexical-analysis-in-python-and-javascript"&gt;custom regexp-based lexer&lt;/a&gt;
isn't very high.&lt;/p&gt;
&lt;p class="last"&gt;That said, it wouldn't make much sense to embark on a journey to rewrite
&lt;em&gt;just&lt;/em&gt; the lexer; the dependency on PLY would still remain, and besides,
PLY's lexer and parser are designed to work well together. So it wouldn't
help me much without tackling the parser beast.&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;I've decided to ask it to the port the parser first, leaving the lexer
alone. This was to split the work into reasonable chunks. Besides, I
figured that the parser is the hard job anyway - if it succeeds in that,
the lexer should be easy. That assumption turned out to be correct.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Python"></category><category term="Machine Learning"></category><category term="Compilation"></category><category term="Recursive descent parsing"></category></entry></feed>