Giorgio Garofalo
Back when I first got access to our family computer at the age of 8, I was instantly fascinated by the number of things one could do with it. Aside from playing Space Cadet, I was obsessed with a single word: create.
I used to spend hours googling how to create characters, how to create games,
how to create websites,
trying to learn everything I could about making things.
It was when I found out about GameMaker that
my passion for software development truly ignited:
I was finally able to create my own games from scratch (kind of),
and that gave me an incredible sense of freedom, accomplishment,
and motivation to keep learning.
It wasn’t long before I wrote my first lines of code, modding Minecraft
in ways that give me a good laugh today.
← that’s me
Fast forward to this day, create is still my favorite word, whether it’s about software, design, or art. I graduated in Computer Science and Engineering, and that gave me a solid foundation to build upon, while keeping my curiosity and passion alive. I’m an advocate of clean code, good design, and user-centered development, and an absolute open source enthusiast.
Now, the pinnacle of my creation journey is this website that you’re browsing. What?, you might ask. It’s such a plain website, where are the flashy animations and creative stuff you’ve been bragging about? Well, this page was made with Quarkdown, my labor of love, that I built from the ground up, backed by the experience that you can only get by creating things over and over again, failing, learning, and improving.
Projects
I lost count of all the projects I’ve started throughout my journey in the field. Some reached completion, many have never seen the light of day, and only a handful became something I’m truly proud of.
Here are my favorites:
Quarkdown
Born as a simple parser and renderer for a custom Markdown flavor, it has evolved into a full-fledged typesetting system.
Focused on ease of use and versatility, it allows generating papers, books, presentations, knowledge bases, and simple websites.Pikt
An esoteric programming language, where programs are represented as images, and each pixel’s color encodes a specific instruction.
Made for fun, it turned out to be surprisingly powerful and Turing-complete, with both a JVM compiler and an interpreter.Animated
A JavaFX library that adds support to implicit animations, typical of modern declarative UI frameworks like Flutter or SwiftUI.Chorus
The first YAML editor built specifically for Minecraft configuration files, with useful completions, previews, and a built-in SFTP and FTP client.
Experience
I’m currently based in San Francisco as a full-stack engineer at Falconer, a startup focusing on building companies’ knowledge bases. I’m mainly contributing to the development of their visual editor.
In the meantime, I’m also completing a Master’s Degree in Computer Science and Engineering at the University of Bologna, after earning my Bachelor’s Degree in the same field in 2024.
For more: Resume
Let’s connect!
Blog
The blurred line between overengineering and anticipating change
From the rise of personal computers up to the last decade, our hardware was so limited that engineers’ main challenge was letting everything fit in and squeezing every single transistor of those chips that now we perceive as prehistoric. To this day I’m still mind-boggled by the optimizations video games came up with between the late 90’s and early 2000’s: to name a few, Quake’s fast inverse square root and Crash Bandicoot literally hacking the PS1 to swap data faster.
However, times change, and software follows: with engineers no longer needing to leverage low-level tricks to achieve the impossible, the focus shifted towards robust, maintainable, predictive, and automatically-tested software.
A shallow dive into internal software quality
When talking about good code, we typically mean code that’s easy to pick up, understand, and change, and that follows the software engineering principles, that include:
- Abstraction: hiding complexity behind a simple interface.
- Modularity: organizing into independent components.
- Separation of concerns: dividing into areas of functionality.
- Anticipation of change: designing to accommodate future modifications.
This is my favorite aspect of software design and the main focus of this post.
When you’re kicking off a new project, it’s tempting to just start coding and get things done quickly. Following these principles can feel like an unnecessary burden, but that’s where things usually backfire: after the initial excitement of dozens of new features, the codebase becomes a tangled mess, and each new feature takes more and more time to implement, as you navigate through your delicious spaghetti code.
I learned about the following chart in my uni classes, and I wish I had seen it sooner:
xychart-beta x-axis "Time (days)" y-axis "Number of features shipped" line [0.0, 6.931471824645996, 10.986123085021973, 13.862943649291992, 16.094379425048828, 17.91759490966797, 19.4591007232666, 20.794414520263672, 21.972246170043945, 23.02585220336914, 23.978954315185547, 24.84906768798828, 25.649492263793945, 26.390573501586914, 27.080501556396484, 27.725887298583984, 28.332134246826172, 28.903717041015625, 29.44438934326172, 29.95732307434082, 30.44522476196289, 30.910425186157227, 31.354942321777344, 31.78053855895996, 32.188758850097656, 32.580963134765625, 32.958370208740234, 33.322044372558594, 33.67295837402344, 34.0119743347168, 34.33987045288086, 34.6573600769043, 34.9650764465332, 35.26360321044922, 35.55348205566406, 35.83518981933594, 36.10917663574219, 36.37586212158203, 36.635616302490234, 36.8887939453125, 37.135719299316406, 37.3766975402832, 37.612003326416016, 37.841896057128906, 38.066627502441406, 38.286415100097656, 38.5014762878418, 38.71200942993164, 38.9182014465332, 39.12023162841797, 39.31825637817383, 39.51243591308594, 39.702919006347656, 39.88983917236328, 40.073333740234375, 40.253517150878906, 40.430511474609375, 40.60443115234375, 40.77537536621094, 40.94344711303711, 41.10873794555664, 41.27134323120117, 41.43134689331055, 41.588829040527344, 41.743873596191406, 41.896549224853516, 42.04692840576172, 42.19507598876953, 42.341064453125, 42.48495101928711, 42.626800537109375, 42.76666259765625, 42.90459442138672, 43.040653228759766, 43.17488098144531, 43.307334899902344, 43.438053131103516, 43.56708908081055, 43.69447708129883, 43.82026672363281, 43.94449234008789, 44.06719207763672, 44.18840408325195, 44.30816650390625, 44.426513671875, 44.54347229003906, 44.659080505371094, 44.77336883544922, 44.8863639831543, 44.99809646606445, 45.10859680175781, 45.21788787841797, 45.32599639892578, 45.43294906616211, 45.53876876831055, 45.64348220825195, 45.747108459472656, 45.84967803955078, 45.95119857788086, 46.05170440673828] line [0.09090909361839294, 0.7272727489471436, 2.454545497894287, 5.818181991577148, 11.363636016845703, 19.636363983154297, 31.18181800842285, 46.54545593261719, 66.2727279663086, 90.90908813476562]
Overengineering is a myth
It’s worth mentioning that, when referring to overengineering, we’re in the context of software architecture. We’re not discussing it in the sense of distributed systems and microservices, as that isn’t my field of expertise.
As an advocate for clean code, I often ask myself whether I’m overengineering things. Whenever I believe I’ve crossed that line, I’m proven wrong as soon as I need to add new functionality on top of what I built earlier.
At that point, I realize the design choices I’d made were actually anticipating change: even without foreseeing the exact change or having any gut feeling about how the software would evolve 6 months down the line, those choices still turn out to be the right ones.
A practical example
When using the Quarkdown CLI,
the quarkdown create command generates a new Quarkdown project with a predefined structure.
About Quarkdown
Quarkdown is a Markdown-based typesetting system, written in Kotlin. It can compile paged documents, slides, and web pages like this one.
The requirements for this command were:
--main-file <name>: option to specify the name of the main file. Default ismain.- Multiple prompts to gather name, description, document type, and other properties.
These properties are then used in the main file as metadata:.docname {...},.docdescription {...},.doctype {...}, and so on. - Unless the
--emptyflag is provided, the main file should contain sample content to help the user grasp the syntax and learn how to use the CLI. Also, animagesdirectory should be created, containing a sample image referenced in the main file.
- my-project
- main.qd
- images
- logo.png
At this point, you might think the implementation is straightforward: create the main file with the specified name, inject properties, and optionally add the sample content and the images directory:
fun createProject(
mainFileName: String,
empty: Boolean,
info: DocumentInfo
) {
val mainFile = File(mainFileName + ".qd")
mainFile.writeText(
"""
.docname {${info.name}}
.docdescription {${info.description}}
.doctype {${info.documentType}}
${if (!empty) """
# ${info.name}
Welcome to Quarkdown!
## Compiling
...
""".trimIndent() else ""}
""".trimIndent()
)
if (!empty) {
val imagesDir = File("images")
imagesDir.mkdir()
// Add sample image to the images directory
}
}Although it’s concise and meets the requirements, this implementation is not maintainable:
- If we want to add more properties in the future (and we will), we’d need to modify
writeText’s raw string. - If we want to change the sample content, we’d need to modify yet another nested raw string.
This is what anticipation of change is all about: designing code that can accommodate future modifications without having to rewrite large portions of it.
Instead, here’s another approach:
- Load the contents from a JTE (Java Template Engine) template, a simple text file with placeholders.
- Instead of creating a file right away, return an
OutputResource, Quarkdown’s abstraction of a file to be created.
fun createProject(
mainFileName: String,
empty: Boolean,
info: DocumentInfo
): OutputResource {
val templateContent = loadTemplate("main.qd.jte")
val renderedContent = renderTemplate(templateContent, info, empty)
return OutputResource(mainFileName + ".qd", renderedContent)
}The final architecture
Well, that’s straightforward and clean. Why would that be overengineered? Because that’s not what I went with! I instead went for this monster of a solution:
ProjectCreator: the orchestrator. It doesn’t know how to create templates or what initial content to include. Instead, it delegates these responsibilities to two injected strategies:ProjectCreatorTemplateProcessorFactory: a factory that creates JTE template processors and injects the document info. It also defines which files to generate viacreateFilenameMappings(), a map of file names to their own template processor.ProjectCreatorInitialContentSupplier: a strategy that provides the sample content and additional resources (e.g. images). The default implementation loads a separate.jtetemplate with the sample code, plus the image.
If--emptyis provided, theEmptysupplier is used, which returns empty content and no resources.
When createResources() is called, ProjectCreator iterates over the factory’s file mappings,
processes each template, and collects all results.
class ProjectCreator(
private val templateProcessorFactory: ProjectCreatorTemplateProcessorFactory,
private val initialContentSupplier: ProjectCreatorInitialContentSupplier,
private val mainFileName: String,
) {
fun createResources(): Set<OutputResource> {
val resources =
this.templateProcessorFactory.createFilenameMappings()
.map { (fileName, processor) ->
TextOutputResource(
fileName ?: mainFileName,
processor.process(),
)
}
return buildSet {
addAll(resources)
addAll(initialContentSupplier.createResources())
}
}
}classDiagram
direction TB
class ProjectCreator {
-mainFileName String
+createResources() Set~OutputResource~
}
class ProjectCreatorTemplateProcessorFactory {
<<interface>>
+createFilenameMappings() Map~String?, TemplateProcessor~
}
class DefaultProjectCreatorTemplateProcessorFactory {
-info DocumentInfo
}
class ProjectCreatorInitialContentSupplier {
<<interface>>
+templateContent String
+createResources() Set~OutputResource~
}
class EmptyProjectCreatorInitialContentSupplier {
+templateCodeContent = null
+createResources() = emptySet
}
class DefaultProjectCreatorInitialContentSupplier {
+templateCodeContent
+createResources() images
}
ProjectCreator --> ProjectCreatorTemplateProcessorFactory
ProjectCreator --> ProjectCreatorInitialContentSupplier
ProjectCreatorTemplateProcessorFactory <|.. DefaultProjectCreatorTemplateProcessorFactory
ProjectCreatorInitialContentSupplier <|.. DefaultProjectCreatorInitialContentSupplier
ProjectCreatorInitialContentSupplier <|.. EmptyProjectCreatorInitialContentSupplierProjectCreator systemWhat proved me wrong
I knew this solution was slightly excessive, but I went with it anyway.
Overengineered, until, about one year later, I added support for a new document type: docs.
This new type allows Quarkdown to generate multi-page technical documentation and wikis,
which have a different structure and different sample content compared to the other types.
This is the structure of a docs project that should be generated:
- my-project
- main.qd
- _setup.qd
- _nav.qd
- page-1.qd
- page-2.qd
- page-3.qd
On top of that, page-N.qd files shouldn’t be generated if the --empty flag is provided.
If I had stuck with the naive implementation, that would have been a nightmare to keep up with: just imagine the amount of conditional logic and nesting to accommodate the new files to be generated.
Instead, with my implementation it was just a matter of adding two small classes:
DocsProjectCreatorTemplateProcessorFactory: overridescreateFilenameMappings()to generate two files instead of one:_setup.qdandmain.qd.DocsProjectCreatorInitialContentSupplier: reuses the default’s sample code content and provides the additional page resources (_nav.qd,page-1.qd,page-2.qd,page-3.qd).
No changes to ProjectCreator itself were needed: it remained completely unaware of docs projects.
The CLI command simply picks the right strategy based on the document type, and the rest falls into place:
return ProjectCreator(
templateProcessorFactory =
when {
isDocs -> DocsProjectCreatorTemplateProcessorFactory(documentInfo)
else -> DefaultProjectCreatorTemplateProcessorFactory(documentInfo)
},
initialContentSupplier =
when {
noInitialContent -> EmptyProjectCreatorInitialContentSupplier()
isDocs -> DocsProjectCreatorInitialContentSupplier()
else -> DefaultProjectCreatorInitialContentSupplier()
},
mainFileName,
)classDiagram
direction LR
ProjectCreator --> ProjectCreatorTemplateProcessorFactory
ProjectCreator --> ProjectCreatorInitialContentSupplier
ProjectCreatorTemplateProcessorFactory <|.. DefaultProjectCreatorTemplateProcessorFactory
ProjectCreatorTemplateProcessorFactory <|.. DocsProjectCreatorTemplateProcessorFactory
ProjectCreatorInitialContentSupplier <|.. DefaultProjectCreatorInitialContentSupplier
ProjectCreatorInitialContentSupplier <|.. EmptyProjectCreatorInitialContentSupplier
ProjectCreatorInitialContentSupplier <|.. DocsProjectCreatorInitialContentSupplierTakeaway
This example shows why I don’t believe overengineering is a thing in software architecture.
Strategy pattern, factory, supplier: they all seemed like overkill for a simple project generator.
But when the requirements evolved, the architecture held up without changes to the core ProjectCreator.
This was a relatively simple and small example, but the same principle easily scales up to larger and more complex systems.
This post was written by a human :)I accidentally ended up in Silicon Valley
By the time I finished high school, I gradually started appreciating the idea of polishing my projects and seeing them evolve over time: I had a few active projects by then, that let me build tiny communities. I realized it was not about writing code: I fell in love with the idea of making something that people would use, carefully writing documentation in a language that I wasn’t even fluent in at the time, getting feedback, and iterating on it. Coming from an art background, where a work is done once it’s on display, this was a new mindset for me.
It was when I was looking for my first internship that I went to a local software house, to have an interview with the founder, a middle-aged Italian man, a big guy with a majestic beard. I was extremely nervous and shaking while he asked me to introduce myself. It was my first ever interview and I had not yet learned how to handle such situations. During the first minute, I went on about my background. “Come on”, I thought before starting, “my background is quite interesting—a young boy learning programming, some GitHub projects with a few dozen stars, I might as well impress him”.
I was not even two minutes in when I went with my go-to: “to me, software development is an art, you create something from a blank canvas” - such a cliche.
He stopped me right there, and firmly corrected me. To him, I was completely wrong:
programming is strict, rigid, structured, it must not be art. Art is freeform, unstructured, chaotic.
Years later, I still vividly remember that moment: it was the first time someone challenged my view on my biggest passion.
I didn’t get the job—I did get an offer, but I wasn’t sure that was the philosophy I wanted to embrace, and turned it down.
I’ve thought about it a lot since then, it made me reflect and shifted my perspective, although I don’t fully agree with him.
That same year, it was time to write my bachelor’s thesis. It was quite straightforward: I chose the professor that taught me the most about software engineering and its best practices as my supervisor, prof. Mirko Viroli,
and emailed him asking for a topic, mentioning I was interested in either utility software or programming languages (very distant fields!).
It didn’t take him longer than 10 minutes to reply, complaining about the complexity of LaTeX: he wanted a new simpler language to write his slides in, and proposed I build it,
with prof. Gianluca Aguzzi as my co-supervisor.
I still wonder if he knew about Typst, but I blindly accepted the challenge, unaware that I was getting into the black hole of typesetting.
I went head-first and within two days I had already designed its syntax: it had to be easy to pick up, so my first choice was building on top of the well-known Markdown.
On the third day I came up with its name: Quarkdown—oh boy I love it.
Months went by, and development was progressing nicely while I was learning a lot, and occasionally swearing at regex patterns, but that’s part of the journey. Every now and then, the big man’s words would echo: programming is not art. I was applying what I’d learned: strict structure, clear rules, no room for ambiguity. I also noticed the software didn’t have to stop at making slides. I mentioned it to the professor, who emailed me back: “Generalizing is costly”. I assumed he meant not to overcomplicate it, but my stubbornness took over, and I kept going. Within a few weeks, Quarkdown could effortlessly produce slides, articles and books. At this exact moment, the post you’re reading was written in Quarkdown.
At that point, I assumed it was time to spread the word: took good care of the documentation, and showcased it on Reddit, where it got a decent amount of attention. A few people reached out to me, praising the idea and suggesting improvements, while some others were quick to point out how needless it was, when there was already good competition in the field. Again, my stubbornness kept me going.
In October 2024 I defended my thesis, and received a standing ovation from the audience. To me, it felt like I finally made it. Little did I know what was coming next.
Of course, as I learned during my youth, it wasn’t the end of the journey: the project had to be polished further. I was determined to keep improving Quarkdown, and that’s what I did.
It was June 3, 2025, that changed my life. A massive traffic spike hit the repo, I would reload the page every few seconds to see the star count going up and up.
Within a day, it had 2000. The next day, 4000. It was insane. I had people from all over the world reaching out to me and praising my work.
Where was all this coming from? Turns out someone I never heard of, who goes by asicsp, had posted about it on Hacker News,
and it got to number one on the front page.
At the end of the same month, I received a message from Dave, founder of Falconer, a startup based in San Francisco
that’s aiming at building a new, powerful knowledge management platform. He had a friend sharing with him the Hacker News post, and was impressed by my work
and wanted to chat.
We hopped on a quick call, I mentioned I was still a student, but he didn’t mind; he openly said they’re looking for people who can ship software
and deliver valuable documentation, and he offered me an on-site position.
Imagine my reaction: a student from Italy, with little professional experience, getting an offer from a Silicon Valley startup. I was over the moon. I told my family about it, and they were warning me: “Why would they want a student from the other side of the world?”, my sister genuinely asked. I understood her concerns. I myself was scared: the company was so new it wasn’t even on Google Maps. But still, I was stubborn enough to have a little faith and I reassured them. Four months later, I finally got my visa and moved to San Francisco.
Today, we’re close to Christmas 2025, and I’ve been here for a month. It has been a wild ride so far and I’m loving every bit of it—the team, the environment, the general vibe of the city, and I can’t believe how far I’ve come since that interview years ago. From a guy stuttering his way through introducing himself, to a software engineer in California, sitting every morning at a desk around open, brilliant minds, people who love their jobs and share my same passion, perhaps seeing programming as an art after all.
If you made it this far, keep believing in what you do—you never know what’s next.
And I haven’t been able to reach out to asicsp yet, so if you’re reading this, I owe you a big one.

Hello, world!
This is my first blog post! This blog, as the rest of the website, is built with Quarkdown. You can find the source code for this website on GitHub.
Whenever I feel like writing something about any topic, I’ll post it here, be it related to software, thoughts, or anything else that comes to mind.