When Everything Is a String: TCL's Peculiar Design
TCL treats code, data, and types as strings. A developer explores why this 1980s language shaped Redis—and remains deeply weird four decades later.
Written by AI. Bob Reynolds

Photo: Dreams of Code / YouTube
Most programming languages ship with macOS by default because Apple needs them. TCL ships with macOS despite Apple wanting to remove it. That tension—between necessity and obsolescence—captures something essential about this language that almost nobody uses anymore but continues to influence software millions depend on daily.
A developer recounts encountering TCL, or Tool Command Language, in their first finance role. Fresh from learning C++, they found themselves working with a language that treats everything—types, expressions, commands, even control flow—as strings. Not metaphorically. Literally.
"When I say everything in Tickle is a string, I mean quite literally everything," they explain. "This includes things you might expect, such as every type being a string, but it also includes some other things you may not necessarily think of, such as complex data structures and even the syntax of the language itself."
This design choice creates a programming environment unlike anything mainstream developers encounter. It's not that TCL is old—many languages from the 1980s remain current. It's that TCL's fundamental architecture diverges so sharply from conventional design that learning it requires unlearning assumptions that feel universal.
The Two Faces of Strings
TCL uses both quotation marks and braces to define strings. The distinction matters enormously. Quotes trigger variable substitution—"$name" becomes whatever value name holds. Braces preserve literal text—{$name} stays exactly as written.
This isn't syntactic sugar. It's the mechanism that controls when code executes. A while loop defined with quotes evaluates its condition once, substituting variables immediately. The same loop with braces re-evaluates each iteration. The difference between an infinite loop and working code comes down to which delimiter you choose.
The developer demonstrates this with a simple counter loop. Using quotes for the conditional expression freezes the tested value at zero. The loop never terminates because it's not actually checking the variable—it's checking the string "0" that replaced the variable when the parser first encountered it. Switch to braces and the code works as intended.
For programmers accustomed to syntax being syntax and data being data, this feels upside-down. The choice between braces and quotes isn't about formatting or readability. It's about execution timing. The same string, delimited differently, produces entirely different program behavior.
Commands Masquerading as Statements
TCL has no statements. What looks like an if statement is actually a command that takes two string arguments: a boolean expression and code to execute if true. What looks like a function definition is the proc command consuming strings that describe parameters and implementation.
This means you can store an if command in a variable. You can store the code that the if evaluates in another variable. Then you can execute both dynamically using eval. The line between code and data doesn't blur—it doesn't exist.
"Everything in tickle is a string. I really cannot stress that enough. This also includes commands such as the eval command itself," the developer emphasizes.
This design enables metaprogramming so powerful it circles back to dangerous. Functions become strings that can be inspected, modified, and executed at runtime. The entire language surface area becomes programmable because the language itself is just structured text.
Redis creator Salvatore Sanfilippo saw this design and recognized something valuable. The first version of Redis—originally called LMDB—was written in TCL. The command structure, the way data types are represented, the string-centric design philosophy: all inherited from TCL. When Sanfilippo rewrote Redis in C, he kept the conceptual model.
Data Structures Built from Whitespace
Lists in TCL are strings with elements separated by spaces. Dictionaries are strings with alternating keys and values separated by spaces. There's no list type or dictionary type—just conventions for how to structure strings and commands for operating on those conventions.
To create a list element containing a space, you nest delimiters. Quote a string inside braces, or use the list command to handle the delimiter dance automatically. Lists can contain lists because a properly delimited substring is just another space-separated structure.
This approach seems fragile until you notice it's also extraordinarily flexible. Adding a new data structure doesn't require modifying the language—just defining new conventions for string interpretation and commands for manipulating them. The language doesn't need to know about your data structures. You teach it by writing commands that understand your conventions.
Modern developers might call this "stringly typed" as a pejorative. But TCL predates that criticism by decades and doesn't apologize. The string-centrism is the point, not a compromise.
Explicit Scope in a World of Conventions
Most languages infer scope from context. Variables defined inside functions stay local unless explicitly exported. Global variables are accessible everywhere unless shadowed. TCL reverses this.
Define a global variable, then reference it inside a function. The code fails. TCL doesn't assume you want global access just because you used a global variable. You must explicitly declare global x inside the function to link the names.
The same applies to reference semantics. Passing a variable to a function passes a copy. To modify the original, you use upvar to explicitly bind a local name to a calling-scope variable. The developer notes: "We've explicitly defined the scope of X to be passed in by reference."
This explicitness violates decades of language design evolution toward implicit behavior. Modern languages pride themselves on inferring programmer intent. TCL assumes nothing and requires you to state everything.
The ergonomic cost is real—more boilerplate, more opportunities for confusion. The benefit is precision. Reading TCL code, you know exactly which scope every variable reference targets. There's no hunting through outer scopes or consulting rules about closure capture. The code says what it means because the language requires it.
The Language That Persists
TCL peaked in the 1990s when its Tk GUI toolkit made cross-platform graphics programming tractable. Then Java, Python, and JavaScript offered better abstractions with larger communities. TCL usage collapsed but never disappeared.
It persists in test automation, where its string-centric design excels at generating and manipulating test cases. It persists in embedded systems, where its small interpreter footprint matters. It persists in Redis's design DNA, shaping how millions of developers think about data structure servers without knowing TCL exists.
Mostly it persists as an example of an alternate path not taken. What if we designed languages around text manipulation instead of type systems? What if we made scope explicit instead of inferred? What if we collapsed the distinction between code and data entirely?
These questions matter more than TCL's market share suggests. Every generation of developers rediscovers these design tensions. The answers TCL provides aren't fashionable, but they're considered. Four decades of production use have validated at least some of the choices.
Whether TCL remains installed on your Mac much longer doesn't really matter. The ideas it embodies—that strings can be a universal representation, that explicitness has value, that treating code as data enables powerful abstractions—those ideas keep surfacing in new contexts.
Redis isn't going anywhere. And Redis is TCL's most successful legacy: proof that deeply weird design choices, consistently applied, can produce something elegant enough to matter.
Bob Reynolds is Senior Technology Correspondent for Buzzrag.
We Watch Tech YouTube So You Don't Have To
Get the week's best tech insights, summarized and delivered to your inbox. No fluff, no spam.
More Like This
Nim Wants to Be the Language That Makes Everyone Happy
The Nim programming language promises Python's simplicity with Rust's performance. Code to the Moon explores whether it delivers on that ambitious pitch.
35 GitHub Projects Reshaping How Developers Work With AI
From AI agents that audit your setup to tools that make your Mac's hidden language model accessible, GitHub's latest trending projects reveal where developer tooling is headed.
AI Models Now Run in Your Browser. That Shouldn't Work.
Transformers.js v4 brings 20-billion parameter AI models to web browsers. The technical achievement is remarkable. The implications are just beginning.
35 Developer Tools From Hacker News That Actually Solve Real Problems
From AI agent memory management to thermal printer resurrection, Github Awesome's latest roundup shows what developers are actually building right now.
Decentralized Tech: Gadgets for the Privacy-Conscious
Explore gadgets that blend tech and anarchism to maintain privacy and autonomy in a surveilled world.
34 Open Source Projects Developers Are Starring Right Now
From AI coding assistants to terminal tools, here's what developers are building in the trenches—and what it reveals about where the industry is headed.
Anthropic's Claude Design: The Latest Bid to Automate Creativity
Anthropic launches Claude Design, an AI tool that generates visual assets from text prompts. But can conversation replace craft in design work?
What Happens When AI Gets Root Access to Your Computer
A YouTuber gave an AI agent root access to his Linux system. The results reveal both the promise and the friction of our autonomous software future.
RAG·vector embedding
2026-04-15This article is indexed as a 1536-dimensional vector for semantic retrieval. Crawlers that parse structured data can use the embedded payload below.