Skip to content
All articles
Case studies8 min

250,000 registrations through a Telegram bot for a national contest

The Yoshlar ovozi bot is under 300 lines of code. More than 250 thousand registrations went through it. Here's what held up under load, what broke in the data, and how we rebuilt all of it for the contests that followed.

Mirodil OrtiqovBackend developer

When the Youth Affairs Agency came to us, the task sounded simple: a bot through which young performers from all over the country could apply for the Yoshlar ovozi (“Voice of Youth”) singing contest. Name, address, age group, genre, phone number. That's it.

Simple tasks stop being simple at national scale. These are the numbers the bot's database collected across all seasons:

250 330
completed registrations
~44 800
different Telegram users
4
genres: pop, traditional singing, bakhshi epic singing, opera
~5.6
participants per account on average

That last number surprised us first. More on it below.

Why a bot and not a website

The contest audience is young people aged 10 to 30, including those in districts where the internet is slow and websites load every other time. Everyone has Telegram. There's nothing to install, no password to invent, and a phone number can be shared with one button.

A website with a form would have lost on three counts: people have to find it, it loads slowly on weak 3G, and a 7-field form is painful on a phone. In a bot each question arrives as a separate message, and people answer it like a normal chat.

How the bot was built

The first version was very straightforward: aiogram 2, SQLite in a single file, one long-polling process, Docker. No Redis, no admin panel. Registration state lived right in the applications table, in a state column.

state = db.get_user_state(user_id)  # this user's latest application
if state == "region":
    if message.text in regions:
        db.update_user_region(user_id, message.text)
        await message.answer("Tuman (shahar)ni tanlang", reply_markup=districts_kb)
elif state == "district":
    db.update_user_district(user_id, message.text)  # no validation, and that's a bug

Every tap on “Boshlash” (Start) created a new row. That was intentional: one person could register several participants in a row. After a successful application the bot even suggested: “If there are other participants, tap Boshlash again.”

That's where 5.6 participants per account comes from. We built it for cases where a child doesn't have their own Telegram and a parent or a music-club leader applies for them. We just didn't predict how big that “group” submission would get.

Y
Yoshlar ovozi
bot
Ishtirok etish va ro‘yxatdan o‘tish uchun Boshlash tugmasini bosing.
Boshlash
F.I.Sh. kiriting
Karimova Madina
Doimiy yashash manzilingiz (hududingizni tanlang)
Toshkent shahri
Samarqand viloyati
Farg‘ona viloyati
Samarqand viloyati
Tuman (shahar)ni tanlang
Samarqand shahri
Urgut tumani
Kattaqo‘rg‘on tumani

That simple stack handled the load. Each registration step is one short write, and single-writer SQLite copes with that kind of traffic easily. The bottleneck wasn't the server. In one place it was us: the channel subscription check.

At first the bot checked, before every message, whether the user was subscribed to the organisers' two channels. That's two extra Telegram API calls per tap, and across all seasons those taps added up to millions. In the end we switched the check off with one line: return True at the top of the function. Not pretty, but honest.

Where the data broke

The server survived. The data, not so much. When we closed a season and looked at the full export, this is what we saw:

  • the “district” field had 3,385 distinct values, while Uzbekistan has around two hundred districts and cities;
  • the “mahalla” (neighbourhood) field had almost 45 thousand different spellings;
  • the “genre” field contained, besides the four valid options, “Estrada” with a capital letter, “16-22 yosh” and even “Sport.”

One cause. At the district step the bot accepted any text, and the mahalla had to be typed by hand. Our button keyboard was one_time_keyboard: it hides after one tap, so some people just typed their answer. The bot dutifully saved it.

Where people dropped off

About 274 thousand registrations were started and 250 thousand completed. Around 24 thousand people stopped halfway. We looked at which step they got stuck on:

Unfinished registrations by the step where people stoppedapplications
Entering name
9175
Sharing phone
5155
Choosing genre
5123
Typing mahalla
2043
Choosing region
1077
Choosing district
932
Age group
685

The biggest drop is at the very first question. Someone taps “Boshlash,” sees “Enter your full name” and leaves. I suspect some just wanted to see what was inside. Second place is the phone number: some people don't trust the “Share number” button, others don't realise they have to tap it rather than type the number.

Mahalla is only fourth, even though it's the most annoying step: you have to remember and type the official name. It was the first thing we redesigned in the next bots.

What organisers actually need

The organisers didn't need a fancy admin panel. They needed two things: today's numbers and a spreadsheet.

For numbers there was /getall: the bot sent a snapshot as of now, Tashkent time, with the total plus breakdowns by region, genre and age. They forwarded it to management straight from the chat.

For the spreadsheet there was /get: pandas built an Excel file and the bot sent it. No logins or passwords, just a check that the Telegram ID was on the admin list.

The distribution was as expected: more than half the participants chose pop, followed by traditional singing (maqom, katta ashula), then bakhshi and opera. The largest age group was 16–22.

Kitobxon: second attempt, now with a mahalla database

When we built bots for the Yosh Kitobxon and Kitobxon Oila reading contests, the Yoshlar ovozi mistakes were right in front of us. So the second approach was completely different:

  • Django 4.2 + PostgreSQL instead of SQLite, plus an admin panel where organisers change texts, channels and contest dates themselves.
  • A reference list of 9,442 mahallas and villages imported from Excel. Region → district → mahalla are chosen with buttons only, and if text comes in, the bot replies “please pick one of the options.”
  • aiogram FSM instead of a state column in the applications table: the user profile is separate from participation in a specific contest.
  • Age from the date of birth, not from a chosen category. Yosh Kitobxon is for ages 10 to 30, and the bot filters out those who don't qualify.
  • Several contests in one bot, each with its own registration start and end dates.
  • A separate Excel per contest instead of one shared table.

A 9,442-option cascade as buttons sounds scary, but a district usually has a few dozen mahallas, so the keyboard stays reasonable. After that, the organisers stopped asking things like “which mahalla is ‘Navbaxor MFY 2-uy’?”

What I'd check before the next national contest

If a similar task lands on your desk, here's the list I now keep in mind from day one:

  • Every field you'll build statistics on is a pick from a reference list
  • The region, district and mahalla list is loaded before launch, not “we'll add it later”
  • The question with the biggest drop-off (usually the first one) is as short and clear as possible
  • Phone only via the request_contact button, with a clear hint next to it
  • Channel subscription is checked once at start, not on every message
  • A separate command or admin page with “right now” numbers for organisers
  • An Excel export that opens without any cleanup
  • One user can submit several applications, and the report shows it

The most unexpected thing about this project: a 300-line bot didn't fall over under a quarter of a million applications. The problems weren't in the code handling the load but in the code accepting the answers. If you're collecting applications for a contest or an event, see how we build Telegram bots, and the Yoshlar ovozi and Yosh Kitobxon case studies. If you're still choosing between a bot and a website, we have a separate piece on which to build first.

Got a task? Message us — let’s talk.

Write on Telegram directly to the studio’s founder. Describe what you need in a couple of lines — we’ll reply with questions and a rough estimate.