linkiblog

the official weblog of linkibol.com

How to Build a Popularity Algorithm You can be Proud of

clock August 2, 2009 10:09 by author linkibol

Many web sites allow users to casts vote on items. These visitors' votes are then often used to detect the items' "popularity" and hence rank the rated items accordingly.

And when "rank" comes into play things gets tricky:

  • The system can have inherent deficiencies in ranking items.
    That is mostly because developers tend to "re-invent the wheel" and throw in their own algorithms instead of basing their calculations on well-established statistical formulae
    (I'll come to that in a moment, just bear with me Wink).
  • There will be people (i.e. spammers) trying to fool the system and try to take their submissions to top.
  • There will be system inefficiencies due to computational complexity.

In this article,

  • I'll try to give examples on how to approach the problem;
  • describe the weakness of each particular approach;
  • and explain how some well-known social community sites implement their ranking algorithms.
  • Finally I'll discuss the current ranking algorithm we use in linkibol.

So let us begin with the most straigh-forward (and thus the most error-prone) approach:

Votes Determine Popularity

We have a database record, whether it be a news story, picture, video, podcast, whatever.

We could order the "Popular" item by number of votes and be done with it.

Popularity = Votes

This algorithm is far from being perfect.

Let me try to explain why:

Say last month was a very active month and popular records in that month received around 100 votes.

This month is not very active in terms of traffic (assume it's a holiday season and your user base is rushing to beaches instead of hysterically seeking for items and casting votes) and as a result you've received an avarage of 30 votes for each item.

Therefore, popular items from last month will probably never see the front page.

Aging Records

Let’s introduce the age of the record as another variable. If the record is newer it should have a higher prominence on the front page, yes?

Popularity = Votes / Record Age

The older the record, the more votes it requires to achieve popularity. But that's not fair:

Again assume someone woke up at 3am and submitted an excellent item to the site, where the rest of community are sleeping instead of being on-line an frantically casting votes.

This excellent item, which has caused our beloved user to wake up in the middle of the night, will not receive the credit it deserves as it fades away due to the dampening effect of time.

So let's look at the age of each record:

Popularity = (V1/A1) + (V2/A2) + … + (Vn/An)

Where

  • Vn is a vote,
  • An is the age of that vote (for example, in minutes).

All the values of all the votes are added to achieve a popularity score.

This seems to solve the previous problem, but introduces a new one:

If a single person votes on a record a year old, his vote will be worh more than 200 votes casted a year ago when the record was created. Old material comes back to haunt the front page.

Let’s take a look again at the age of the record …

Dampening The Weighted Votes By Record Age

So let us introduce the age of the record into the equation:

Popularity = [ (V1/A1) + (V2/A2) + … + (Vn/An) ] / Record Age

This formula dampens votes we've calculated above basd on the items's age, which prevents old stories from leaping back to the front page.

It seems to solve our problem but it's computationally inefficient:

It is difficult to implement in a practical system.

Moreover it's overly-dependent on time, which also adds to the algorithm's inefficiency:

Every second, both the age of votes and the record age change. Thus, the popularity needs to be calculated for all the records in the database for every few seconds (or minutes depending on how active the site is). That's intolerable in a high-traffic system where resource utilization is of premium importance.

Since this algorithm is inapplicable let's look at another basic example: 

Using simple + and - Votes

If you like an item, rate it “plus”. If you don’t like it, give it a “minus”.

The rating of an item would then be: number of positive votes divided by number of total votes.

Now if you want to rank the items based on this simple equation, the following happens:

Assume you have on item with a rating of 0.93, based on hundreds of votes.

Now another new item is rated by a total of 2 visitors (or even just one), and they rate it +. The vote goes straight to the first position in the ranking, as its rating is 100%!

What we want is this:

  • If there is only few votes, then these votes should count less
  • when there are many votes and we can trust that this is how the public feels about it.

That is "certainty" or "believability" of the vote should depend on the number of votes that record has received.

Thus, we want to calculate a corrected rating that somehow takes the weight of votes into account:
The more votes an item has, the closer the corrected rating value would be to the uncorrected rating value.

That's not a new thing though. The math for this has been worked out hundreds of years ago. It is called "bayesian average".

The bayesian rating can be given as:

 br = ( (C* avg_rating) + (this_num_votes * this_rating) ) / (C + this_num_votes)

Where

  • C: is an ad-hoc constant. If it's high. It will require more votes for the adjusted (dampened) rating of an item to approach its original unadjusted value
  • avg_rating: The average rating of each item (again, of those that have num_votes>0)
  • this_num_votes: number of votes for this item
  • this_rating: the rating of this item

There is no point requiring 1000 votes for the item to rank 60% when each item only gets a handful of votes in average. So if the system receives less votes in general, C should be smaller.

To make the system adaptive we can assign C a self-adjusting value, such as "average number of votes". Which makes our formula.

br = ( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) ) / (avg_num_votes + this_num_votes)
 

This formula works statistically better than the former one (i.e. dampenint the votes by record age).

That damping by record age algorithm was very dependent on time. On the contrary, this rating formula is totally independent of time.

It can be fine-tuned by adding

  • Dampening effect of time,
  • Trustablility (karma) of voters 
  • Page views and site activity at the time of vote
  • If we're voting a link, metadata for that link such as google pagerank, alexa rank etc.

Let's move to another approach:

Popularity = Lower bound of Wilson score confidence interval for a Bernoulli parameter

Lower bound of what?!

We need to balance the proportion of positive ratings with the uncertainty of a small number of observations.
Fortunately, the math for this was worked out in 1927 by Edwin B. Wilson:

Popularity is:

Where:

  • p: is the average rating of the item normalized to [0, 1] interval.
  • z(alpha/2): is the 1-(alpha/2) th quantile of the standard normal distribution.
    It can be calculated easily using the following ruby method: Statistics2.pnormaldist(1-alpha/2)
  • n: is the number of votes.

The algorithm is sound, and computationally feasible. You can adjust the dampening effect of the normal distribution by changing alpha.

How Do We Rank Things in linkibol?

Our preference in linkibol is to use an adjusted Bayesian Rating algorithm.

I'll be explaining linkibol's popularity algorithm in a moment, but before that let's how a look at how other giants calculate popularity.

Y Combinator's Hacker News:

Popularity = (p - 1) / (t + 2)^1.5

Votes divided by age factor.
Where

p : votes (points) from users.
t : time since submission in hours.

p is subtracted by 1 to negate submitter's vote.
Age factor is (time since submission in hours plus two) to the power of 1.5.

Reddit:

Description:

First of all, the time 7:46:43 am on December 8th 2005 is a constant used to determine the relative age of a submission.

The time the story was submitted minus the constant date is ts. ts works as the force that pulls the stories down the frontpage.
y represents the relationship of up votes to down votes.

45000 is the amount of seconds in 12.5 hours. This constant is used in combination with yts to "water down" votes as they are made farther and farther from the time the article was submitted.

The issue is discussed thoroughly in Y Combinator.

StumbleUpon

Popularity = (Initial stumbler audience / # domain) + ((% stumbler audience / # domain) + organic bonus – nonfriend) – (% stumbler audience + organic bonus) + N

Description:

The initial stumbler "power" (Audience of the initial stumbler divided by the amount of times that stumbler has stumbled the given domain) is added to the sum of all the subsequent stumblers' powers.
 
Subsequent stumbler power is ((Percentage of audience stumbler makes up divided by the number of times given stumbler has stumbled domain) + a predetermined power boost for using the toolbar - a predetermined power drain if stumblers are connected)  + (% of the stumbler audience + a predetermined boost for using the toolbar)

N is a "safety variable" so that the assumed algorithm is flexible. It represents a random number.

Del.icio.us:

Del.icio.us has the simplest formula of all. 

Popularity = (Amount of times story has been bookmarked in the last 3600 seconds)

The algorithm is self explanatory. The more people bookmark an item in a given time window, the more popular it is.

How Do We Implement Popularity in linkibol ?

To answer this question we need to give some details on how voting works in linkibol:

  • Votes are not absolute in linkibol. Each linkibol user's vote weight depend on their karma.
    Users who have more karma (i.e. popular and active users) can cast more points for votes.
  • The sum of all vote points given to a link is that link's "linkipoint".
  • Users can cast negative votes, as well as positive votes.

Here's linkibol's popularity calculation formula:

(click on the image for an enlarged version)
A bit mouthful I know Smile It took us years to simplify it to this form.

But it's a robust algorithm. And it's computatioanly efficient once you cache certain non-dynamic parts of it.

Let me explain the terms in this equation:

  • nvotes : total number of votes so far
  • nlinks : total number of links
  • nvotes(r) : number of votes cast to rth link.
  • deltarank(k, m) :  rank increment caused by kth vote that is casted to mth link.
  • nsaves(i) : number of users that save ith link to their linkibol.
  • a: save exponent (an ad-hoc value close to 1)
  • age(i) : the difference (in days) between date link added and current date.
  • b: decay exponent (an ad-hoc value close to 0) 

When you examine the formula closely you'll see that it' nothing more than an adaptive bayesian rating formula multiplied by two coefficients

  • the number of users who save the link. The more an item is shared, the more popular it is.
  • and the age of the link: the older an item the less popular it becomes.

Ideally the adjusted ratings should be calculated regularly for "all" the links in linkibol.

However, since this will bring a huge computational burden, we eliminate the problem by discarding unaltered links.
That is: if a link's linkipoint does not change at all, we don't calculate its popularity rank.
In other words, if a link does not receive any votes, we trust its most recently calculated rank.

For this we have a "dirty" flag on the links table. When someone (even someone anonymous) casts a vote to a link, it becomes dirty.
Then a batch thread scans all dirty links and recalculates and updates their ranks periodically.

Bottom Line:
Calculating popularity is not as easy as it sems Wink.



Asp.net Localization: How to Access a Local Resource from Outside the Page

clock August 2, 2009 10:07 by author linkibol

While working on the localization of linkibol.com, I found myself in need of creating a method to reach the LocalResources of a page from  outside the context of the ASP.net Page.

My initial atempt was to pass the user control as a parameter to the method and call GetLocalResourceObject inside the method like

...
public static void UpdateControlFromLocalResource(WebControl userControl, string key)
{
    var resourceObject = userControl.GetLocalResourceObject(key);
}
...
However this was not possible, because GetLocalResourceObject is a protected method of TemplateControl and therefore cannot be accessed from outside the Page context.

The resolution was to assign a delegate GetLocalResourceObjectDeletage, pass it as a parameter to the method and call the method indirectly via the delegate.

Assigning a Delegate to TemplateControl.GetLocalResourceObject

To make a long story short, here's the final version of the method:
...
namespace InterfaceCube
{
public class TemplateHelper
{
		//A delegate for GetLocalResourceObject method
public delegate object GetLocalResourceObjectDeletage(string key);
public static void UpdateControlFromLocalResource(Control userControl, List<string> attributes, 
GetLocalResourceObjectDeletage getLocalResourceObject)
{
foreach(var attribute in attributes)
{
var resourceValue = getLocalResourceObject(Thread.CurrentThread.CurrentCulture).ToString();
if((userControl is Literal))
{
switch(attribute)
{
case AttributeName.TEXT:
((Literal)userControl).Text = resourceValue;
break;
default:
break;
}
}
else
{
switch(attribute)
{
case AttributeName.TEXT:
((HyperLink)userControl).Text = resourceValue;
break;
case AttributeName.ALT:
((HyperLink)userControl).ToolTip = resourceValue;
break;
case AttributeName.HREF:
((HyperLink)userControl).NavigateUrl = resourceValue;
break;
default:
break;
}
}
}
}
}
}
...

where AttributeName is just a simple struct

namespace InterfaceCube
{
public struct AttributeName
{
public const string TEXT = "Text";
public const string ALT = "ToolTip";
public const string HREF = "NavigateUrl";
}
}

Currently the helper method is being used only for Literal and HyperLink instances (that's why there is an "if((userControl is Literal))" fork on the code), and for the time being it suits our needs.

 

By the way, IntefaceCube much extensive closed-source user interface and templating library that linkibol uses. I plan to distribute it with a proper open-source license, but it's currently too dependent to linkibol's core library, I should sort this dependency issue at first hand to make InterfaceCube a standalone library.

...

The UpdateControlFromLocalResource method, as its name implies, updates various attributes of the user control passed as parameter
by retrieving it from a local resource.


I needed this helper method because linkibol has lost of custom controls that extend Page, and User Control. If I had not used this delegating approach, the only solution was to copy/paste the very same code to every single custom control used. This would obviously not be a good practice of code re-use.

And here is how the method is used:

var attributes = new List<string> {AttributeName.ALT, AttributeName.HREF};
TemplateHelper.UpdateControlFromLocalResource(SampleLink, attributes, GetLocalResourceObject);

where SampleLink is a asp:HyperLink user control.

This one line of code magically gets "SampleLink.ToolTip" and "SampleLink.Text" values from the page's local resource bag and updates ToolTip and Text attribues of the link.

Why not Use the Traditional meta:resourcekey Binding?

This usage has several advantages over the traditional usage (i.e. using meta:resourcekey="LocalResourceName" on the front-end code, or using GetLocalResourceObject("resourcename") on the codebehind):

  1. This technique clearly isolates string literals. Without this helper class, we would be required to call

    GetLocalResourceObject("SampleLink.ToolTip").ToString();

    method to get the value from the resource; where "SampleLink.ToolTip" would be a hardcoded string literal.

    It's a good programming practice to avoid string and number literals.

    In fact, C# library coding standards explicitly recommends the library author to "Avoid declaring inline string literals. Instead use Constants, Resources, Registry or other data sources". (also see design guidelines for .net library developers and idesign's C# coding standards)

    It's okay to use a string literal only the string will never change AND it is obvious from the context of the code what the string is meant for.
    One other exception is logging, debugging and tracing strings. Other than these, all numeric and string literals should be externalized to a resource of some kind.

  2. Instead of binding the link using meta:resourcekey like

    <asp:HyperLink ID="SampleLink" CssClass="active" meta:resourcekey="SampleLink" NavigateUrl="/" runat="server" />

    we've essentially removed the meta:resourckey attribue from the user control. TemplateHelper handles this binding.

    <asp:HyperLink ID="SampleLink" CssClass="active" NavigateUrl="/" runat="server" />

    this will decrease the level of dependency of the page's markup to the resource files.

  3. By not using meta:resourcekey literals we eliminate the possibility of making a typo while assigning those resources.

    If, by mistake you mistype the attribute name of a control in the local resource file;  say, for instance, you mistyped SampleLink.Text as SamleLink.Text on the resource file; you'll get a runtime Exception in TemplateHelper.UpdateControlFromLocalResource method. Your program will crash loudly -- which is a good thing.

    On the other hand if you assign meta:resourcekey="SamleLink" (notice the mistyped "Samle") this will not generate any errors and your web application will fail silently.

    If there are a few localizable resources at hand; this will not be that hard to detect. But, if you have hundreds of controls to localize (as I do now); even if you have %100 unit test coverage  it is quite probable that this typo may get unnoticed and leak into production.

That'a all for now.

Just wanted to present an alternative point of view on localizing asp.net web applications.

Now I'm going back to localizing linkibol Wink

Cheers.



URL Rewriting in linkibol (behind the scenes)

clock August 2, 2009 10:04 by author linkibol

linkibol web application runs on an IIS7 windows server box.

Althogh there exists a URL rewriting module for IIS7, we preferred enhancing our formerly-written HTTP module (IndigoWrite.RewriteHttpModule) over it, because of its lesser performance overhead.

IndigoWrite is currently a closed-source library. However we plan to distrubute it under an open-source license. It requires some workout beforehand though. It's is on our to do list none the less.

Adding a managed module in IIS7 is a piece of cake. First, you choose "Modules" section for the website using IIS Manager snap in:

 Then you select "Add Managed Module" action:

The module we added to linkibol is "IndigoWrite.RewriteHttpModule".

 Here follows the edit screen for our managed module:

After having added the module, we bound the requests to "link.bul" paths to a managed HTTPHandler. This can be easily done by first selecting HTTPHandler for the site from the ISS Management snap in:

 

and then choosing "Add Managed Handler".

We named the handler LinkBulManagedHandler and associated it with "link.bul" pathname. So any request that is sent to http://www.linkibol.com/link.bul/* is handled by this handler.

Actually this handler is a PageHandler that is created by a PageHandlerFactory. PageHandlerFactory Class creates instances of classes that inherit from the Page class and implement the IHttpHandler interface. Instances are created dynamically to handle requests for ASP.NET files. The PageHandlerFactory class is the default handler factory implementation for ASP.NET pages.

You can change mapings, verbs, and access restriction for the assigned handler if you like:

Here's how the entire HTTP Pipeline flows:

  1. Web page request comes from browser.
  2. IIS associates "link.bul" file extension to a "System.Web.UI.PageHandlerFactory", provided with ASP.NET.
  3. IIS" forwards the request to the ASP.NET worker process (ASPNET_WP.EXE or W3P.EXE).
  4. Then the worker process loads HTTPRuntime and passes the request to it. Thus, HTTP Pipelining has begun.
  5. HTTPRuntime uses HttpApplicationFactory to either create or reuse the HTTPApplication object.
  6. HTTPRuntime creates HTTPContext for the current request. HTTPContext internally maintains HTTPRequest and HTTPResponse.
  7. HTTPRuntime also maps the HTTPContext to the HTTPApplication which handles the application level events.
  8. Then HTTPApplication runs the HTTPModules for the page requests (in this case RewriteHTTPModule is run along with other registered modules (if any)).
    An HTTP module is an assembly that is called on every request that is made to the application. Since HTTP modules are called as part of the ASP.NET request pipeline, they have
    access to life-cycle events throughout the request. HTTP modules let you examine incoming and outgoing requests and take action based on the request).
  9. Then HttpApplication creates the handler for the request using System.Web.UI.PageHandlerFactory. The handler, in this particular scenario, is nothing but a System.Web.UI.Page page implementing the IHTTPHandler interface. IHttpHandlers are responsible to process the request and generate corresponding response messages. This is the last stage of HTTP Pipelining.
  10. Once the request leaves the HTTPPipeline, page-level events begin.
  11. Page Events are as follows: PreInit, Init, InitComplete, PreLoad, Load, Control events (Postback events), Load Complete, PreRender, SaveStateComplete,  Render and Unload.
  12. After having gone through the above events, the response is sent back to the IIS which in turn sends the response to the client browser. 
That's what runs "behind the scenes" to generate a simple rewrite request.


linkibol v2 - Code Name: Transformation

clock August 2, 2009 10:01 by author linkibol

Change is in the Air

Due to the high load on linkibol, we recently (it has been less than a month) migrated to a new server to increase responsiveness of the application.

In the meantime we also did a lot of optimization on the back end code.
Some of which are:

  • Injecting caching mechanisms to speed things up,
  • Totally rewriting the database abstraction layer,
  • Upgrading the Database (from MySQL 4.1 to MySQL 5.x)
  • Upgrading the application framework (From .net framework 1.x to .net framework 3.5),
  • Upgrading the server environment (From Windows 2003 Server to Windows Server 2008),
  • Adding more processing power (in terms of RAM and CPU)
We also have a code name for this new release:

linkibol v.2 -- code name Transformation

to indicate the migration period we had been in.

Change brings momentum, momentum brings temporary instability, and instability brings bugs.

That's why most of the corporations are afraid of change.

However without change you are stale. Hence we are not afraid of change.

Fixed Tiny Little Bugs

Recently we've fixed most of the dead links (many of them were pointing to help pages and widgets).

Currently we are working on tiny little bugs that arose from the huge transformation period we had been in. After all bugs are settled down, we will be working on adding new features. And as always, we'd love to listen to your thoughts.

Thanks for your interest and support in linkibol.



A quick glance at the new linkibol.com

clock August 2, 2009 09:54 by author linkibol

We are still working on the localization of linkibol.

And while localizing we are doing certain usability re-alignments based on user feedback.

Here's a sneak peak of what the English version of the site will look like.

screenshot

You can click on the image to see an enlarged version.

 

Edit: You can also visit linkibol Transformation set on flickr to see an updated list of screenshots.

 

Cheers.

 



Migrated to a new server...

clock August 2, 2009 02:46 by author linkibol

Due to the excessive traffic we receive (thank you!), we've migrated linkibol to a faster web server (by the way, I'd be glad to hear your comments on the performance of the new server: Is it better? Are the any unexpected things you experience while browsing linkibol?)

Anyway, it was almost a flawless migration, except for this blog Frown

The blog version we use on our former server failed to run; so we were forced to upgrade this blog to the most up to date version of BlogEngine.net.

None of the blog posts were lost in this transition period. However I will need to reload them from bacckups.

I just wanted to inform that full recovery of posts may take some time; and the permalinks to the posts may change.

Thank you,
Volkan Özçelik,
Founder, linkibol.com



Want to Have Some Atom?

clock March 15, 2009 11:31 by author linkibol

URL Rewriting and Syndication in linkibol

 

note to self: those mappings have changed in the new version. I'll update this post and put a link to the updated mappings.

 

Finally linkibol's url rewriting module is up ad runing Smile.

With the help of this module you can...

Aside from the above link patterns, in the upcoming releases, we plan to implement...

  • A filter mechanism o get links by invididual users (like http://www.linkibol.com/link.bul/linkibolcu/{username} (this feature is already present, however there is a tiny little bug, that prevents this from working properly -- will be working on this soon)
  • A filter mechanism to get links by multiple users (like http://www.linkibol.com/link.bu/linkibolcu/{username1}/{username2}
  • A filter mechanism to get links by multiple tags (like http://www.linkibol.com/link.bul/etiket/{tag1}/{tag2}
  • A filter mechanism to get links by a combination of the two (like http://www.linkibol.com/link.bul/linkibolcu/{username1}/{username2}/etiket/{tag1}/{tag2} )

I'd appreciate any comments and suggestions on these.

...

For the techy geeks out there, all of these are possible by implementing an Http Module to IIS. I'll elaborate on it in the followng post. Just keep tuned in Wink



Hello world, hello stars, hello universe!

clock February 25, 2009 11:47 by author linkibol

Welcome, welcome...

... and welcome to linkiblog the official weblog of linkibol.com.

linkibol (pronounced lean-kee-ball) means "plenty of links" in Turkish. 

linkibol is not widely known internationally, because it does not (currently) provide a (g)localized version. Actually, around 90% or linkibol's traffic comes directly from Turkey.

You ask why?

Until recently,

  • we've been busy adding new features,
  • fixing bugs,
  • and building a community around linkibol.

There are still a lot of features to add and bugs to fix. Even so, we consider linkibol a quite mature product, and think that it's the right time to show the rest of the world what linkibol is.

So What the Heck is linkibol Anyway?

linkibol is an ajax-based social web application that can be used for tagging, rating, and voting links and stories.

Curently linkibol is Turkey's first and leading social bookmarking platform has around 9000 unique visitors daily (don't get deceived by Alexa ratings, they don't truely reflect linkibol's traffic, because of site's ajaxified nature).

What Makes linkibol Unique?

Aside from its %100 ajax user interface, there are several key poins that make linkibol stand out from the crowd:

  • Focus on Usability and Speed:

    Instead of bloating application with mostly unused features, we continuously focus on simplification:
    • linkibol is usable in it's default state. Users are encouraged to explore and find hidden gems that are not necessary for the average joe, but make linkibol extremely useful for the power user (some of which we will be sharing in this blog).
    • Which is another way of saying "while using linkibol the user should not think" -- she should not feel in need of looking at help pages (which most users don't do anyway ;) )
    • To speed things up we cache responses, cache images, cache scripts, and lazy load components whenever possible. (We will dive more deep into the speed issue when we examine architecture of linkibol in upcoming posts).

  • Everyone can Contribute:

    Another point makes linkibol different from it's counterparts is the fact that anybody can vote on linkibol. Anonymous votes are totally allowed.

    • Votes are Relative:

      "In democracies people are equal, but some people are more equal."

      So in linkibol.

      The more active a user is, the more vote she has. This makes spamming on linkibol virtually impossible. Here's how the logic flows:

      • (1) Nobody likes spammers.
      • (2) Each linkibol user has to prove trustworthiness to the community to gain popularity (i.e. increase their karma).
      • (3) To promote a link, and make it popular; you should have a high karma.
      • (3) Since nobody likes spammers (1), spammers' karmas are negative. Thus spammers are not popular.
      • (4) If you are not popular, it's very very hard for you to promote your link.
      • (5) It is really hard to spam on linkibol
      • QED.

  • Contributions Become Instantly Live on the Front Page:

    You don't wait to become popular for your story to be heard. As soon as you publish your story, it is immediately promoted on the linkibol front page.

    So how do we cope with trolls and spammers (given that we don't even have a single moderator)?

    Well, that's where votes are relative part comes into play:

    If the contributor is a spammer, she'll gain such a negetive karma that the system (depending on certain metrics) will decide to suspend her membership.

    And guess what: When a membership is suspended, all of the links added by the member is gone for good (for the interested, this pruning operation is done a scheduled deamon thread on a regular basis).

    To sum up:

    • If you submit quality content, you'll be promoted.
    • Whereas, if you submit bullshit, you'll be kicked out of linkibol forever.

    That's linkibol's "what you give is what you get" democracy:

What can I do with linkibol?

linkibol is a social hub where you can save, discover, share, explore links and stories, and have fun ;)

By using linkibol you can; 

  • Save all your bookmarks in a central location and access them anywhere around the world.
  • Group your bookmarks by labeling them.
  • Share your bookmarks with your friends.
  • Comment on links, see what others have written as comments.
  • Explore the linkiverse to find out more links that match your field of interest.

What's Up with this Blog?

This blog (linkiblog) will be mainly about:

  • adventures we've experienced,
  • obstacles we've got on,
  • wisdom and understanding we've gained,
  • fun and pleasure we've been living; while adding value to linkibol.

The main language of this blog will be English, while we will be occasionally posting bilingual articles (in Turkish and English) to announce important news and features.

So What do You Think?

We're working on an English version of linkibol.
However, if you are familiar with social bookmarking linkibol's, the interface is quite intuitive.
You can give it a whirl by registering for free in less than 10 seconds.

I'd be more than happy if you share your comments and ideas below.

Best,
Volkan Özçelik,

Founder, linkibol